/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* 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.querydsl.codegen;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.primitives.Primitives;
import com.mysema.codegen.model.*;
import com.querydsl.core.util.ReflectionUtils;
/**
* {@code TypeFactory} is a factory class for {@link Type} instances
*
* @author tiwe
*
*/
public final class TypeFactory {
private static final Type ANY = new TypeExtends(Types.OBJECT);
private final Map<List<?>, Type> cache = Maps.newHashMap();
private final List<Class<? extends Annotation>> entityAnnotations;
private final List<AnnotationHelper> annotationHelpers = Lists.newArrayList();
private final Set<Class<?>> embeddableTypes = Sets.newHashSet();
private boolean unknownAsEntity = false;
private Function<EntityType, String> variableNameFunction;
public TypeFactory() {
this(Lists.<Class<? extends Annotation>>newArrayList(), DefaultVariableNameFunction.INSTANCE);
}
public TypeFactory(List<Class<? extends Annotation>> entityAnnotations) {
this(entityAnnotations, DefaultVariableNameFunction.INSTANCE);
}
public TypeFactory(List<Class<? extends Annotation>> entityAnnotations, Function<EntityType, String> variableNameFunction) {
this.entityAnnotations = entityAnnotations;
this.variableNameFunction = variableNameFunction;
}
public EntityType getEntityType(Class<?> cl) {
java.lang.reflect.Type generic = cl;
if (cl.getTypeParameters().length > 0) {
generic = new ParameterizedTypeImpl(cl, cl.getTypeParameters());
}
return (EntityType) get(true, cl, null, generic);
}
public Type get(Class<?> cl) {
return get(cl, cl);
}
public Type get(Class<?> cl, java.lang.reflect.Type genericType) {
return get(isEntityClass(cl), cl, null, genericType);
}
public Type get(Class<?> cl, AnnotatedElement annotated, java.lang.reflect.Type genericType) {
return get(isEntityClass(cl), cl, annotated, genericType);
}
public Type get(boolean entity, Class<?> cl, java.lang.reflect.Type genericType) {
return get(entity, cl, null, genericType);
}
public Type get(boolean entity, Class<?> cl, AnnotatedElement annotated, java.lang.reflect.Type genericType) {
ImmutableList.Builder<Object> keyBuilder = ImmutableList.builder().add(cl).add(genericType);
AnnotationHelper annotationHelper = null;
Annotation selectedAnnotation = null;
if (annotated != null) {
for (Annotation annotation : annotated.getDeclaredAnnotations()) {
for (AnnotationHelper helper : annotationHelpers) {
if (helper.isSupported(annotation.annotationType())) {
keyBuilder.add(annotation.annotationType());
selectedAnnotation = annotated.getAnnotation(annotation.annotationType());
annotationHelper = helper;
keyBuilder.add(helper.getCustomKey(selectedAnnotation));
break;
}
}
}
}
List<?> key = keyBuilder.build();
if (cache.containsKey(key)) {
Type value = cache.get(key);
if (entity && !(value instanceof EntityType)) {
value = new EntityType(value, variableNameFunction);
cache.put(key, value);
}
return value;
} else {
Type value = create(entity, cl, annotationHelper, selectedAnnotation, genericType, key);
cache.put(key, value);
return value;
}
}
private Type create(boolean entity, Class<?> cl, AnnotationHelper annotationHelper, Annotation annotation, java.lang.reflect.Type genericType,
List<?> key) {
if (cl.isPrimitive()) {
cl = Primitives.wrap(cl);
}
Type value;
Type[] tempParams = (Type[]) Array.newInstance(Type.class,
ReflectionUtils.getTypeParameterCount(genericType));
cache.put(key, new ClassType(cl, tempParams));
Type[] parameters = getParameters(cl, genericType);
if (cl.isArray()) {
Type componentType = get(cl.getComponentType());
if (cl.getComponentType().isPrimitive()) {
componentType = Types.PRIMITIVES.get(componentType);
}
value = componentType.asArrayType();
} else if (cl.isEnum()) {
value = new ClassType(TypeCategory.ENUM, cl);
} else if (Number.class.isAssignableFrom(cl) && Comparable.class.isAssignableFrom(cl)) {
value = new ClassType(TypeCategory.NUMERIC, cl, parameters);
} else if (entity) {
value = createOther(cl, entity, annotationHelper, annotation, parameters);
} else if (Map.class.isAssignableFrom(cl)) {
value = new SimpleType(Types.MAP, parameters[0], asGeneric(parameters[1]));
} else if (List.class.isAssignableFrom(cl)) {
value = new SimpleType(Types.LIST, asGeneric(parameters[0]));
} else if (Set.class.isAssignableFrom(cl)) {
value = new SimpleType(Types.SET, asGeneric(parameters[0]));
} else if (Collection.class.isAssignableFrom(cl)) {
value = new SimpleType(Types.COLLECTION, asGeneric(parameters[0]));
} else {
value = createOther(cl, entity, annotationHelper, annotation, parameters);
}
if (genericType instanceof TypeVariable) {
TypeVariable tv = (TypeVariable) genericType;
if (tv.getBounds().length == 1 && tv.getBounds()[0].equals(Object.class)) {
value = new TypeSuper(tv.getName(), value);
} else {
value = new TypeExtends(tv.getName(), value);
}
}
if (entity && !(value instanceof EntityType)) {
value = new EntityType(value, variableNameFunction);
}
return value;
}
private Type asGeneric(Type type) {
if (type.getParameters().size() == 0) {
int count = type.getJavaClass().getTypeParameters().length;
if (count > 0) {
return new SimpleType(type, new Type[count]);
}
}
return type;
}
private Type createOther(Class<?> cl, boolean entity, AnnotationHelper annotationHelper, Annotation annotation, Type[] parameters) {
TypeCategory typeCategory = TypeCategory.get(cl.getName());
if (annotationHelper != null) {
typeCategory = annotationHelper.getTypeByAnnotation(cl, annotation);
} else if (!typeCategory.isSubCategoryOf(TypeCategory.COMPARABLE) && Comparable.class.isAssignableFrom(cl)
&& !cl.equals(Comparable.class)) {
typeCategory = TypeCategory.COMPARABLE;
} else if (embeddableTypes.contains(cl)) {
typeCategory = TypeCategory.CUSTOM;
} else if (typeCategory == TypeCategory.SIMPLE && entity) {
typeCategory = TypeCategory.ENTITY;
} else if (unknownAsEntity && typeCategory == TypeCategory.SIMPLE && !cl.getName().startsWith("java")) {
typeCategory = TypeCategory.CUSTOM;
}
return new ClassType(typeCategory, cl, parameters);
}
private Type[] getParameters(Class<?> cl, java.lang.reflect.Type genericType) {
int parameterCount = ReflectionUtils.getTypeParameterCount(genericType);
if (parameterCount > 0) {
return getGenericParameters(cl, genericType, parameterCount);
} else if (Map.class.isAssignableFrom(cl)) {
return new Type[]{Types.OBJECT, Types.OBJECT};
} else if (Collection.class.isAssignableFrom(cl)) {
return new Type[]{Types.OBJECT};
} else {
return new Type[0];
}
}
private Type[] getGenericParameters(Class<?> cl, java.lang.reflect.Type genericType,
int parameterCount) {
Type[] types = new Type[parameterCount];
for (int i = 0; i < types.length; i++) {
types[i] = getGenericParameter(cl, genericType, i);
}
return types;
}
@SuppressWarnings("rawtypes")
private Type getGenericParameter(Class<?> cl, java.lang.reflect.Type genericType, int i) {
java.lang.reflect.Type parameter = ReflectionUtils.getTypeParameter(genericType, i);
if (parameter instanceof TypeVariable) {
TypeVariable variable = (TypeVariable) parameter;
Type rv = get(ReflectionUtils.getTypeParameterAsClass(genericType, i), null, parameter);
return new TypeExtends(variable.getName(), rv);
} else if (parameter instanceof WildcardType
&& ((WildcardType) parameter).getUpperBounds()[0].equals(Object.class)
&& ((WildcardType) parameter).getLowerBounds().length == 0) {
return ANY;
} else {
Type rv = get(ReflectionUtils.getTypeParameterAsClass(genericType, i), null, parameter);
if (parameter instanceof WildcardType) {
rv = new TypeExtends(rv);
}
return rv;
}
}
private boolean isEntityClass(Class<?> cl) {
for (Class<? extends Annotation> clazz : entityAnnotations) {
if (cl.isAnnotationPresent(clazz)) {
return true;
}
}
return embeddableTypes.contains(cl);
}
public void extendTypes() {
for (Map.Entry<List<?>, Type> entry : cache.entrySet()) {
if (entry.getValue() instanceof EntityType) {
EntityType entityType = (EntityType) entry.getValue();
if (entityType.getProperties().isEmpty()) {
for (Type type : cache.values()) {
if (type.getFullName().equals(entityType.getFullName()) && type instanceof EntityType) {
EntityType base = (EntityType) type;
for (Property property : base.getProperties()) {
entityType.addProperty(property);
}
}
}
}
}
}
}
public void setUnknownAsEntity(boolean unknownAsEntity) {
this.unknownAsEntity = unknownAsEntity;
}
public void addEmbeddableType(Class<?> cl) {
embeddableTypes.add(cl);
}
public void addAnnotationHelper(AnnotationHelper annotationHelper) {
annotationHelpers.add(annotationHelper);
}
}