/*
* 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.EntityView;
import com.blazebit.persistence.view.EntityViewInheritance;
import com.blazebit.persistence.view.EntityViewInheritanceMapping;
import com.blazebit.reflection.ReflectionUtils;
import javax.persistence.metamodel.ManagedType;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
*
* @author Christian Beikov
* @since 1.2.0
*/
public class ViewMapping implements Comparable<ViewMapping> {
private final Class<?> entityViewClass;
private final EntityView mapping;
private final MetamodelBuildingContext context;
private final MethodAttributeMapping idAttribute;
private final Map<String, MethodAttributeMapping> attributes;
private final Map<ParametersKey, ConstructorMapping> constructors;
private final String inheritanceMapping;
private final Set<ViewMapping> inheritanceSubtypes;
private final Set<ViewMapping> inheritanceSupertypes;
private final InheritanceViewMapping defaultInheritanceViewMapping;
private final Set<InheritanceViewMapping> inheritanceViewMappings;
private ManagedViewTypeImpl<?> viewType;
public ViewMapping(Class<?> entityViewClass, EntityView mapping, MetamodelBuildingContext context, MethodAttributeMapping idAttribute, Map<String, MethodAttributeMapping> attributes, Map<ParametersKey, ConstructorMapping> constructors, String inheritanceMapping, Set<ViewMapping> inheritanceSubtypes) {
this.entityViewClass = entityViewClass;
this.mapping = mapping;
this.context = context;
this.idAttribute = idAttribute;
this.attributes = attributes;
this.constructors = constructors;
this.inheritanceMapping = inheritanceMapping;
this.inheritanceSubtypes = inheritanceSubtypes;
this.inheritanceSupertypes = new HashSet<>();
this.inheritanceViewMappings = new HashSet<>();
inheritanceViewMappings.add(defaultInheritanceViewMapping = new InheritanceViewMapping(this, inheritanceSubtypes));
}
public InheritanceViewMapping getDefaultInheritanceViewMapping() {
return defaultInheritanceViewMapping;
}
public Class<?> getEntityViewClass() {
return entityViewClass;
}
public EntityView getMapping() {
return mapping;
}
public MethodAttributeMapping getIdAttribute() {
return idAttribute;
}
public Map<String, MethodAttributeMapping> getAttributes() {
return attributes;
}
public Map<ParametersKey, ConstructorMapping> getConstructors() {
return constructors;
}
public String getInheritanceMapping() {
if (inheritanceMapping == null && !inheritanceSupertypes.isEmpty()) {
Class<?> entityClass = mapping.value();
// Check all super type inheritance mappings. If we encounter that a super type uses
// an entity class that is not a proper super type of our entity class, we can't infer a type inheritance mapping
for (ViewMapping supertypeMapping : inheritanceSupertypes) {
Class<?> supertypeEntityClass = supertypeMapping.getMapping().value();
if (!supertypeEntityClass.isAssignableFrom(entityClass) || supertypeEntityClass == entityClass) {
return inheritanceMapping;
}
}
// If we get here, we know that our entity class type is a proper subtype of all super type inheritance mappings
return "TYPE(this) = " + context.getEntityMetamodel().entity(entityClass).getName();
}
return inheritanceMapping;
}
public Set<ViewMapping> getInheritanceSubtypes() {
return inheritanceSubtypes;
}
public Set<ViewMapping> getInheritanceSupertypes() {
return inheritanceSupertypes;
}
public Set<InheritanceViewMapping> getInheritanceViewMappings() {
return inheritanceViewMappings;
}
public ManagedViewTypeImpl<?> getManagedViewType() {
if (viewType == null) {
if (idAttribute != null) {
return viewType = new ViewTypeImpl<Object>(this, context);
} else {
return viewType = new FlatViewTypeImpl<Object>(this, context);
}
}
return viewType;
}
public static ViewMapping initializeViewMappings(Class<?> entityViewRootClass, Class<?> entityViewClass, MetamodelBuildingContext context, Map<Class<?>, ViewMapping> viewMappings, Set<Class<?>> dependencies, AttributeMapping originatingAttributeMapping) {
ViewMapping existingMapping = viewMappings.get(entityViewClass);
if (existingMapping != null) {
return existingMapping;
}
EntityView entityView = AnnotationUtils.findAnnotation(entityViewClass, EntityView.class);
Class<?> entityClass = entityView.value();
ManagedType<?> managedType = context.getEntityMetamodel().managedType(entityClass);
// Inheritance
EntityViewInheritance inheritanceAnnotation = entityViewClass.getAnnotation(EntityViewInheritance.class);
EntityViewInheritanceMapping inheritanceMappingAnnotation = entityViewClass.getAnnotation(EntityViewInheritanceMapping.class);
String inheritanceMapping;
if (inheritanceMappingAnnotation != null) {
inheritanceMapping = inheritanceMappingAnnotation.value();
} else {
inheritanceMapping = null;
}
Set<ViewMapping> inheritanceSubtypes;
Set<Class<?>> subtypeClasses;
if (inheritanceAnnotation == null) {
inheritanceSubtypes = Collections.emptySet();
subtypeClasses = Collections.emptySet();
} else if (inheritanceAnnotation.value().length == 0) {
inheritanceSubtypes = new TreeSet<>();
subtypeClasses = initializeSubtypes(entityViewRootClass, entityViewClass, context, viewMappings, dependencies, originatingAttributeMapping, inheritanceSubtypes, context.findSubtypes(entityViewClass), false);
} else {
inheritanceSubtypes = new LinkedHashSet<>();
subtypeClasses = initializeSubtypes(entityViewRootClass, entityViewClass, context, viewMappings, dependencies, originatingAttributeMapping, inheritanceSubtypes, new HashSet<>(Arrays.asList(inheritanceAnnotation.value())), true);
}
// Attributes
MethodAttributeMapping idAttribute = null;
// We use a tree map to get a deterministic attribute order
Map<String, MethodAttributeMapping> attributes = new TreeMap<>();
// Deterministic order of methods for #203
Method[] methods = entityViewClass.getMethods();
Set<String> handledMethods = new HashSet<String>(methods.length);
Set<String> concreteMethods = new HashSet<String>(methods.length);
// mark concrete methods as handled
for (Method method : methods) {
if (!Modifier.isAbstract(method.getModifiers()) && !method.isBridge()) {
handledMethods.add(method.getName());
concreteMethods.add(method.getName());
}
}
for (Class<?> c : ReflectionUtils.getSuperTypes(entityViewClass)) {
for (Method method : c.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers()) && Modifier.isAbstract(method.getModifiers()) && !method.isBridge()) {
final String methodName = method.getName();
if (handledMethods.add(methodName)) {
String attributeName = AbstractMethodAttribute.extractAttributeName(entityViewClass, method, context);
if (attributeName != null && !attributes.containsKey(attributeName)) {
Annotation mapping = AbstractMethodAttribute.getMapping(attributeName, method, context);
if (mapping != null) {
MethodAttributeMapping attribute = new MethodAttributeMapping(entityViewClass, managedType, mapping, context, attributeName, method);
attributes.put(attributeName, attribute);
if (attribute.isId()) {
idAttribute = attribute.handleReplacement(idAttribute);
}
}
}
} else if (!concreteMethods.contains(methodName)) {
// Check if the attribute definition is conflicting
String attributeName = AbstractMethodAttribute.extractAttributeName(entityViewClass, method, context);
Annotation mapping = AbstractMethodAttribute.getMapping(attributeName, method, context);
// We ignore methods that only have implicit mappings
if (mapping instanceof MappingLiteral) {
continue;
}
MethodAttributeMapping originalAttribute = attributes.get(attributeName);
MethodAttributeMapping attribute = new MethodAttributeMapping(entityViewClass, managedType, mapping, context, attributeName, method);
MethodAttributeMapping newAttribute = attribute.handleReplacement(originalAttribute);
if (newAttribute != originalAttribute) {
attributes.put(attributeName, newAttribute);
if (newAttribute.isId()) {
idAttribute = newAttribute.handleReplacement(idAttribute);
}
}
}
}
}
}
// Deterministic order of constructors
Map<ParametersKey, ConstructorMapping> constructors = new TreeMap<>();
for (Constructor<?> constructor : entityViewClass.getDeclaredConstructors()) {
String constructorName = MappingConstructorImpl.extractConstructorName(constructor);
ConstructorMapping constructorMapping = new ConstructorMapping(entityViewClass, managedType, constructorName, constructor, context);
constructors.put(new ParametersKey(constructor.getParameterTypes()), constructorMapping);
}
ViewMapping viewMapping = new ViewMapping(entityViewClass, entityView, context, idAttribute, attributes, constructors, inheritanceMapping, inheritanceSubtypes);
for (ViewMapping subtype : inheritanceSubtypes) {
subtype.inheritanceSupertypes.add(viewMapping);
}
viewMappings.put(entityViewClass, viewMapping);
for (MethodAttributeMapping attributeMapping : attributes.values()) {
attributeMapping.initializeViewMappings(entityViewRootClass, viewMappings, dependencies);
}
for (ConstructorMapping constructorMapping : constructors.values()) {
constructorMapping.initializeViewMappings(entityViewRootClass, viewMappings, dependencies);
}
// Cleanup dependencies after constructing the view type
dependencies.removeAll(subtypeClasses);
return viewMapping;
}
private static Set<Class<?>> initializeSubtypes(Class<?> entityViewRootClass, Class<?> entityViewClass, MetamodelBuildingContext context, Map<Class<?>, ViewMapping> viewMappings, Set<Class<?>> dependencies, AttributeMapping originatingAttributeMapping, Set<ViewMapping> inheritanceSubtypes, Set<Class<?>> subtypeClasses, boolean explicit) {
for (Class<?> subtypeClass : subtypeClasses) {
if (!dependencies.add(subtypeClass) && subtypeClass != entityViewClass) {
originatingAttributeMapping.circularDependencyError(dependencies);
return subtypeClasses;
}
}
for (Class<?> subtypeClass : subtypeClasses) {
if (entityViewClass == subtypeClass) {
if (explicit) {
context.addError("Entity view type '" + entityViewClass.getName() + "' declared itself in @EntityViewInheritance as subtype which is not allowed!");
}
continue;
}
if (explicit) {
if (!entityViewClass.isAssignableFrom(subtypeClass)) {
context.addError("Entity view subtype '" + subtypeClass.getName() + "' was explicitly declared as subtype in '" + entityViewClass.getName() + "' but isn't a Java subtype!");
}
}
ViewMapping subtypeMapping = ViewMapping.initializeViewMappings(entityViewRootClass, subtypeClass, context, viewMappings, dependencies, originatingAttributeMapping);
inheritanceSubtypes.add(subtypeMapping);
inheritanceSubtypes.addAll(subtypeMapping.getInheritanceSubtypes());
}
return subtypeClasses;
}
@Override
public int compareTo(ViewMapping o) {
return getEntityViewClass().getName().compareTo(o.getEntityViewClass().getName());
}
}