/*
* Copyright 2014 - 2017 Blazebit.
*
* 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.blazebit.persistence.view.impl.metamodel;
import com.blazebit.annotation.AnnotationUtils;
import com.blazebit.persistence.view.BatchFetch;
import com.blazebit.persistence.view.CollectionMapping;
import com.blazebit.persistence.view.MappingCorrelated;
import com.blazebit.persistence.view.MappingCorrelatedSimple;
import com.blazebit.persistence.view.MappingInheritance;
import com.blazebit.persistence.view.MappingInheritanceMapKey;
import com.blazebit.persistence.view.MappingInheritanceSubtype;
import com.blazebit.persistence.view.MappingParameter;
import com.blazebit.persistence.view.MappingSingular;
import com.blazebit.persistence.view.MappingSubquery;
import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodCollectionAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodListAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodMappingSingularAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.CorrelatedMethodSetAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodCollectionAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodListAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodMapAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodSetAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.MappingMethodSingularAttribute;
import com.blazebit.persistence.view.impl.metamodel.attribute.SubqueryMethodSingularAttribute;
import com.blazebit.reflection.ReflectionUtils;
import javax.persistence.metamodel.ManagedType;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
/**
*
* @author Christian Beikov
* @since 1.2.0
*/
public class MethodAttributeMapping extends AttributeMapping {
private final String attributeName;
private final Method method;
public MethodAttributeMapping(Class<?> entityViewClass, ManagedType<?> managedType, Annotation mapping, MetamodelBuildingContext context, String attributeName, Method method) {
super(entityViewClass, managedType, mapping, context);
this.attributeName = attributeName;
this.method = method;
}
public String getAttributeName() {
return attributeName;
}
public Method getMethod() {
return method;
}
@Override
public String getErrorLocation() {
return getLocation(attributeName, method);
}
public static String getLocation(String attributeName, Method method) {
return "attribute " + attributeName + "[" + methodReference(method) + "]";
}
@Override
public CollectionMapping getCollectionMapping() {
return MetamodelUtils.getCollectionMapping(method);
}
@Override
public BatchFetch getBatchFetch() {
return AnnotationUtils.findAnnotation(method, BatchFetch.class);
}
public MethodAttributeMapping handleReplacement(AttributeMapping original) {
if (original == null) {
return this;
}
if (!(original instanceof MethodAttributeMapping)) {
throw new IllegalStateException("Tried to replace attribute [" + original + "] with method attribute: " + this);
}
MethodAttributeMapping originalAttribute = (MethodAttributeMapping) original;
// If the mapping is the same, just let it through
if (mapping.equals(originalAttribute.getMapping())) {
return originalAttribute;
}
// Also let through the attributes that are "specialized" in subclasses
if (method.getDeclaringClass() != originalAttribute.getMethod().getDeclaringClass()
&& method.getDeclaringClass().isAssignableFrom(originalAttribute.getMethod().getDeclaringClass())) {
// The method is overridden/specialized by the method of the existing attribute
return originalAttribute;
}
// If the original is implicitly mapped, but this attribute isn't, we have to replace it
if (originalAttribute.getMapping() instanceof MappingLiteral) {
return this;
}
context.addError("Conflicting attribute mapping for attribute '" + attributeName + "' at the methods [" + methodReference(method) + ", " + methodReference(originalAttribute.getMethod()) + "] for managed view type '" + entityViewClass.getName() + "'");
return originalAttribute;
}
private static String methodReference(Method method) {
return method.getDeclaringClass().getName() + "." + method.getName();
}
// If you change something here don't forget to also update ParameterAttributeMapping#getParameterAttribute
@SuppressWarnings("unchecked")
public <X> AbstractMethodAttribute<? super X, ?> getMethodAttribute(ManagedViewTypeImpl<X> viewType) {
if (attribute == null) {
Class<?> attributeType = ReflectionUtils.getResolvedMethodReturnType(viewType.getJavaType(), method);
boolean correlated = mapping instanceof MappingCorrelated || mapping instanceof MappingCorrelatedSimple;
// Force singular mapping
if (AnnotationUtils.findAnnotation(method, MappingSingular.class) != null || mapping instanceof MappingParameter) {
if (correlated) {
attribute = new CorrelatedMethodMappingSingularAttribute<X, Object>(viewType, this, context);
return (AbstractMethodAttribute<? super X, ?>) attribute;
} else {
attribute = new MappingMethodSingularAttribute<X, Object>(viewType, this, context);
return (AbstractMethodAttribute<? super X, ?>) attribute;
}
}
if (Collection.class == attributeType) {
if (correlated) {
attribute = new CorrelatedMethodCollectionAttribute<X, Object>(viewType, this, context);
} else {
attribute = new MappingMethodCollectionAttribute<X, Object>(viewType, this, context);
}
} else if (List.class == attributeType) {
if (correlated) {
attribute = new CorrelatedMethodListAttribute<X, Object>(viewType, this, context);
} else {
attribute = new MappingMethodListAttribute<X, Object>(viewType, this, context);
}
} else if (Set.class == attributeType || SortedSet.class == attributeType || NavigableSet.class == attributeType) {
if (correlated) {
attribute = new CorrelatedMethodSetAttribute<X, Object>(viewType, this, context);
} else {
attribute = new MappingMethodSetAttribute<X, Object>(viewType, this, context);
}
} else if (Map.class == attributeType || SortedMap.class == attributeType || NavigableMap.class == attributeType) {
if (correlated) {
context.addError("The mapping defined on method '" + viewType.getJavaType().getName() + "." + method.getName() + "' uses a Map type with a correlated mapping which is unsupported!");
attribute = null;
} else {
attribute = new MappingMethodMapAttribute<X, Object, Object>(viewType, this, context);
}
} else if (mapping instanceof MappingSubquery) {
attribute = new SubqueryMethodSingularAttribute<X, Object>(viewType, this, context);
} else if (correlated) {
attribute = new CorrelatedMethodMappingSingularAttribute<X, Object>(viewType, this, context);
} else {
attribute = new MappingMethodSingularAttribute<X, Object>(viewType, this, context);
}
}
return (AbstractMethodAttribute<? super X, ?>) attribute;
}
@Override
protected Class<?> resolveType() {
return ReflectionUtils.getResolvedMethodReturnType(entityViewClass, method);
}
@Override
protected Class<?> resolveKeyType() {
Class<?> attributeType = ReflectionUtils.getResolvedMethodReturnType(entityViewClass, method);
Class<?>[] typeArguments = ReflectionUtils.getResolvedMethodReturnTypeArguments(entityViewClass, method);
// Force singular mapping
if (typeArguments.length == 0 || AnnotationUtils.findAnnotation(method, MappingSingular.class) != null || AnnotationUtils.findAnnotation(method, MappingParameter.class) != null || !Map.class.isAssignableFrom(attributeType)) {
return null;
}
return typeArguments[0];
}
@Override
protected Class<?> resolveElementType() {
Class<?> attributeType = ReflectionUtils.getResolvedMethodReturnType(entityViewClass, method);
Class<?>[] typeArguments = ReflectionUtils.getResolvedMethodReturnTypeArguments(entityViewClass, method);
// Force singular mapping
if (typeArguments.length == 0 || AnnotationUtils.findAnnotation(method, MappingSingular.class) != null || AnnotationUtils.findAnnotation(method, MappingParameter.class) != null) {
return attributeType;
}
return typeArguments[typeArguments.length - 1];
}
@Override
protected Map<Class<?>, String> resolveInheritanceSubtypeMappings() {
MappingInheritance inheritance = AnnotationUtils.findAnnotation(method, MappingInheritance.class);
if (inheritance != null) {
Class<?> baseType = null;
if (!inheritance.onlySubtypes()) {
baseType = resolveType();
}
return resolveInheritanceSubtypeMappings(baseType, inheritance.value());
}
return resolveInheritanceSubtypeMappings(null, null);
}
@Override
protected Map<Class<?>, String> resolveKeyInheritanceSubtypeMappings() {
MappingInheritanceMapKey inheritance = AnnotationUtils.findAnnotation(method, MappingInheritanceMapKey.class);
if (inheritance != null) {
Class<?> baseType = null;
if (!inheritance.onlySubtypes()) {
baseType = resolveKeyType();
}
return resolveInheritanceSubtypeMappings(baseType, inheritance.value());
}
return null;
}
@Override
protected Map<Class<?>, String> resolveElementInheritanceSubtypeMappings() {
MappingInheritance inheritance = AnnotationUtils.findAnnotation(method, MappingInheritance.class);
if (inheritance != null) {
Class<?> baseType = null;
if (!inheritance.onlySubtypes()) {
baseType = resolveElementType();
}
return resolveInheritanceSubtypeMappings(baseType, inheritance.value());
}
return resolveInheritanceSubtypeMappings(null, null);
}
private Map<Class<?>, String> resolveInheritanceSubtypeMappings(Class<?> baseType, MappingInheritanceSubtype[] subtypes) {
if (subtypes == null) {
MappingInheritanceSubtype subtype = AnnotationUtils.findAnnotation(method, MappingInheritanceSubtype.class);
if (subtype == null) {
return null;
} else {
subtypes = new MappingInheritanceSubtype[]{ subtype };
}
}
Map<Class<?>, String> mappings = new HashMap<>(subtypes.length);
if (baseType != null) {
mappings.put(baseType, null);
}
for (MappingInheritanceSubtype subtype : subtypes) {
String mapping = subtype.mapping();
if (mapping.isEmpty()) {
mapping = null;
}
mappings.put(subtype.value(), mapping);
}
return mappings;
}
}