All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.restup.mapping.fields.MappedField Maven / Gradle / Ivy

There is a newer version: 0.0.5
Show newest version
package com.github.restup.mapping.fields;

import static com.github.restup.util.ReflectionUtils.makeAccessible;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import com.github.restup.annotations.field.CaseInsensitive;
import com.github.restup.annotations.field.Immutable;
import com.github.restup.annotations.field.Param;
import com.github.restup.annotations.field.Relationship;
import com.github.restup.annotations.field.RelationshipType;
import com.github.restup.mapping.fields.composition.CaseSensitivity;
import com.github.restup.mapping.fields.composition.Identifier;
import com.github.restup.mapping.fields.composition.Immutability;
import com.github.restup.mapping.fields.composition.MapField;
import com.github.restup.mapping.fields.composition.ReflectMappedField;
import com.github.restup.mapping.fields.composition.ReflectMappedMethod;
import com.github.restup.mapping.fields.composition.ReflectReadableMappedMethod;
import com.github.restup.mapping.fields.composition.ReflectWritableMappedMethod;
import com.github.restup.mapping.fields.composition.Relation;
import com.github.restup.path.MappedFieldPathValue;
import com.github.restup.registry.Resource;
import com.github.restup.registry.ResourceRegistry;
import com.github.restup.util.Assert;
import com.github.restup.util.ReflectionUtils;
import com.github.restup.util.ReflectionUtils.BeanInfo;
import com.github.restup.util.ReflectionUtils.PropertyDescriptor;

/**
 * Captures meta data about fields for mapping api
 */
public interface MappedField extends ReadWriteField {

    //TODO doc

    /**
     * 
     * @param mappedField providing relationship name
     * @param resource providing default name
     * @return The relationship name from the mappedField or the resource name by default
     */
    static String getRelationshipName(MappedField mappedField, Resource resource) {
        String name = mappedField.getRelationshipName();
        if (StringUtils.isEmpty(name)) {
            name = resource.getName();
        }
        return name;
    }

    static Object toCaseInsensitive(MappedFieldPathValue mfpv, Object value) {
        return toCaseInsensitive(mfpv.getMappedField().getCaseSensitivity(), value);
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    static Object toCaseInsensitive(CaseSensitivity caseSensitivity, Object value) {
        if (value instanceof Collection) {
            Collection result = (Collection) ReflectionUtils.newInstance(value.getClass());
            for (Object o : (Collection) value) {
                result.add(toCaseInsensitive(caseSensitivity, o));
            }
            return result;
        } else if (value instanceof String) {
            String s = (String) value;
            return caseSensitivity.isLowerCased() ? s.toLowerCase() : s.toUpperCase();
        }
        return null;
    }

    static boolean isCaseInsensitive(MappedField mf) {
        return mf != null && mf.isCaseInsensitive();
    }

    static MappedField getIdentityField(List> attributes) {
    		return attributes.stream()
    			.filter(MappedField::isIdentifier)
    			.findFirst()
    			.get();
    }

    public static  BasicMappedField.Builder builder(Class type) {
        return new BasicMappedField.Builder(type);
    }

    public static BasicMappedField.Builder builder(Type type) {
        return new BasicMappedField.Builder<>(type);
    }

    Type getType();
    
	T newInstance();

    String getBeanName();

    String getApiName();

    String getPersistedName();

	boolean isCollection();

    boolean isTransientField();

    boolean isApiProperty();

    Identifier getIdentifier();

    default boolean isIdentifier() {
        return getIdentifier() != null;
    }

    default boolean isIdentifierNonAutoGeneratedValuePermitted() {
        return applyToIdentifier(Identifier::isNonAutoGeneratedValuePermitted);
    }

    /**
     * null safe. apply f to {@link #getIdentifier()}
     * 
     * @param  result of function
     * @param f function to apply
     * @return result of function
     */
    default  R applyToIdentifier(Function f) {
        Identifier identifier = getIdentifier();
        return identifier == null ? null : f.apply(identifier);
    }

    CaseSensitivity getCaseSensitivity();

    default boolean isCaseInsensitive() {
        return applyToCaseSensitivity(CaseSensitivity::isCaseInsensitive) == Boolean.TRUE;
    }

    default String getCaseInsensitiveSearchField() {
        return applyToCaseSensitivity(CaseSensitivity::getSearchField);
    }

    /**
     * null safe. apply f to {@link #getCaseSensitivity()}
     * 
     * @param  result of function
     * @param f function to apply
     * @return result of function
     */
    default  R applyToCaseSensitivity(Function f) {
        CaseSensitivity caseSensitivity = getCaseSensitivity();
        return caseSensitivity == null ? null : f.apply(caseSensitivity);
    }

    Immutability getImmutability();

    default boolean isImmutable() {
        return applyToImmutability(Immutability::isImmutable) == Boolean.TRUE;
    }

    default boolean isImmutabilityErrorOnUpdateAttempt() {
        return applyToImmutability(Immutability::isErrorOnUpdateAttempt) == Boolean.TRUE;
    }

    default boolean isImmutabilityIgnoreUpdateAttempt() {
        return applyToImmutability(Immutability::isIgnoreUpdateAttempt) == Boolean.TRUE;
    }

    /**
     * null safe. apply f to {@link #getImmutability()}
     * 
     * @param  result of function
     * @param f function to apply
     * @return result of function
     */
    default  R applyToImmutability(Function f) {
        Immutability immutability = getImmutability();
        return immutability == null ? null : f.apply(immutability);
    }

    String[] getParameterNames();

    Relation getRelationship();

    boolean isRelationship();

    @Override
    boolean isDeclaredBy(Class clazz);

    default String getRelationshipName() {
        return applyToRelationship(Relation::getName);
    }

    default String getRelationshipResource(ResourceRegistry registry) {
        return applyToRelationship(r -> r.getResource(registry));
    }

    default String getRelationshipJoinField() {
        return applyToRelationship(Relation::getJoinField);
    }

    default RelationshipType getRelationshipType() {
        return applyToRelationship(Relation::getType);
    }

    /**
     * null safe. apply f to {@link #getRelationship()}
     * 
     * @param  result of function
     * @param f function to apply to the relationship
     * @return result of f
     */
    default  R applyToRelationship(Function f) {
        Relation relation = getRelationship();
        return relation == null ? null : f.apply(relation);
    }

    public final static class Builder {

        private Type type;
        private String beanName;
        private String apiName;
        private String persistedName;
        private boolean apiProperty;
        private boolean transientField;
        private Identifier identifier;
        private CaseSensitivity caseSensitivity;
        private Relation relation;
        private Immutability immutability;
        private String[] parameterNames;
        private Field field;
        private Method getter;
        private Method setter;

        private Class genericType;

        public Builder(Type type) {
            this.type = type;
        }

        private Builder me() {
            return this;
        }

        public Builder field(Field field) {
            this.field = makeAccessible(field);
            return me();
        }

        public Builder getter(Method getter) {
            this.getter = makeAccessible(getter);
            return me();
        }

        public Builder setter(Method setter) {
            this.setter = makeAccessible(setter);
            return me();
        }

        @SuppressWarnings("rawtypes")
        public Builder genericType(Type genericType) {
            if (genericType instanceof Class) {
                return genericType((Class) genericType);
            }
            return me();
        }

        public Builder genericType(Class genericType) {
            this.genericType = genericType;
            return me();
        }

        public Builder apiProperty(boolean apiProperty) {
            this.apiProperty = apiProperty;
            return me();
        }

        public Builder transientField(boolean transientField) {
            this.transientField = transientField;
            return me();
        }

        public Builder immutable(Immutable immutable) {
            return immutability(Immutability.getImmutability(immutable));
        }

        public Builder immutability(Immutability immutability) {
            this.immutability = immutability;
            return me();
        }

		public Builder caseSensitiviteField(String searchField) {
            return caseSensitivity(CaseSensitivity.builder()
            		.searchField(searchField));
		}

        public Builder caseInsensitive(CaseInsensitive caseInsensitive) {
            return caseSensitivity(CaseSensitivity.getCaseSensitivity(caseInsensitive));
        }

        public Builder caseSensitivity(CaseSensitivity.Builder caseSensitivity) {
            return caseSensitivity(caseSensitivity.build());
        }

        public Builder caseSensitivity(CaseSensitivity caseSensitivity) {
            this.caseSensitivity = caseSensitivity;
            return me();
        }

        public Builder relationship(Relationship relationship) {
            return relation(Relation.getRelation(relationship));
        }
        
        public Builder relationshipTo(String resource) {
        		return relationshipTo(resource, "id");
        }
        
        public Builder relationshipTo(String resource, String joinField) {
        		return relation(Relation.builder()
        				.joinField(joinField)
					.resource(resource).build());
        }

        public Builder relation(Relation relation) {
            this.relation = relation;
            return me();
        }

        public Builder param(Param param) {
            return parameterNames(param == null ? null : param.value());
        }

        public Builder parameterNames(String... parameterNames) {
            this.parameterNames = parameterNames;
            return me();
        }

        public Builder identifier(Identifier identifier) {
            this.identifier = identifier;
            return me();
        }

        public Builder idField(boolean isIdField) {
            return identifier(isIdField ? Identifier.builder().build() : null);
        }

        @SuppressWarnings({"unchecked", "rawtypes"})
        public MappedField build() {

	    		if ( this.apiProperty ) {
	    			Assert.notNull(apiProperty, "api name is required for api fields");
	    		}
	    		if ( ! this.transientField ) {
	    			Assert.notNull(persistedName, "persisted name is required for non transient fields");
	    		}
	    	

            ReadableField readable = null;
            WritableField writable = null;
            if (readable == null) {
                if (field != null) {
                    readable = ReflectMappedField.of(field);
                } else if (getter != null && setter != null) {
                    readable = ReflectMappedMethod.of(getter, setter);
                } else if (getter != null) {
                    readable = ReflectReadableMappedMethod.of(getter);
                } else {
                    // if writable is explicitly configured (not null) then
                    // we will use setter or used default MapField readable
                    if (setter != null) {
                        writable = (WritableField) ReflectWritableMappedMethod.of(setter);
                    } else {
                        readable = MapField.of(beanName);
                    }
                }
            }

            if (writable == null && readable instanceof WritableField) {
                writable = (WritableField) readable;
            }

            Immutability immutability = this.immutability;
            if (identifier != null && immutability == null) {
                immutability = Immutability.builder().build();
            }

            if ( type instanceof Class ) {
            		Class clazz = (Class) type;
	            if (Iterable.class.isAssignableFrom(clazz)) {
	            		boolean collection = Collection.class.isAssignableFrom(clazz);
	                return new BasicIterableField(clazz, beanName, apiName, persistedName, identifier, collection, apiProperty, transientField, caseSensitivity, relation, immutability, parameterNames, readable, writable, genericType);
	            }
            }
            return new BasicMappedField(type, beanName, apiName, persistedName, identifier, false, apiProperty, transientField, caseSensitivity, relation, immutability, parameterNames, readable, writable);
        }

		public void accept(MappedFieldBuilderVisitor[] visitors, BeanInfo bi,
                PropertyDescriptor pd) {
            // visit builders for customization
            if (visitors != null) {
                for (MappedFieldBuilderVisitor visitor : visitors) {
                    accept(visitor, bi, pd);
                }
            }
        }

        public void accept(MappedFieldBuilderVisitor visitor, BeanInfo bi,
                PropertyDescriptor pd) {
            visitor.visit(this, bi, pd);
        }

        public String getBeanName() {
            return beanName;
        }

        public Builder beanName(String beanName) {
            this.beanName = beanName;
            return me();
        }

        public String getApiName() {
            return apiName;
        }

        public Builder apiName(String apiName) {
            this.apiName = apiName;
            return apiProperty(StringUtils.isNotEmpty(apiName));
        }

        public String getPersistedName() {
            return persistedName;
        }

        public Builder persistedName(String persistedName) {
            this.persistedName = persistedName;
            return transientField(StringUtils.isEmpty(persistedName));
        }

		public void anonymousMapping() {
			this.beanName = nvl(beanName, apiName, persistedName);
			if ( ! transientField ) {
				persistedName = nvl(persistedName, beanName);
			}
			if ( apiProperty ) {
				apiName = nvl(apiName, beanName);
			}
		}

		private String nvl(String... vals) {
			for (String s : vals) {
				if ( StringUtils.isNotEmpty(s) ) {
					return s;
				}
			}
			return null;
		}
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy