/* * 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.apt; import java.lang.annotation.Annotation; import java.util.*; import javax.annotation.Nullable; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.*; import com.google.common.base.Function; import com.mysema.codegen.model.*; import com.querydsl.codegen.*; import com.querydsl.core.annotations.QueryExclude; /** * {@code ExtendedTypeFactory} is a factory for APT-based inspection {@link Type} creation * * @author tiwe * */ final class ExtendedTypeFactory { private final Map<List<String>, Type> typeCache = new HashMap<List<String>, Type>(); private final Map<List<String>, EntityType> entityTypeCache = new HashMap<List<String>, EntityType>(); private final Type defaultType; private final Set<Class<? extends Annotation>> entityAnnotations; private final ProcessingEnvironment env; private final TypeMirror objectType, numberType, comparableType, collectionType, setType, listType, mapType; private final TypeMappings typeMappings; private final QueryTypeFactory queryTypeFactory; private boolean doubleIndexEntities = true; private Function<EntityType, String> variableNameFunction; private final TypeVisitor<Type, Boolean> visitor = new SimpleTypeVisitorAdapter<Type, Boolean>() { @Override public Type visitPrimitive(PrimitiveType primitiveType, Boolean p) { switch (primitiveType.getKind()) { case BOOLEAN: return Types.BOOLEAN; case BYTE: return Types.BYTE; case SHORT: return Types.SHORT; case INT: return Types.INTEGER; case LONG: return Types.LONG; case CHAR: return Types.CHARACTER; case FLOAT: return Types.FLOAT; case DOUBLE: return Types.DOUBLE; default: return null; } } private Type getPrimitive(PrimitiveType primitiveType) { switch (primitiveType.getKind()) { case BOOLEAN: return Types.BOOLEAN_P; case BYTE: return Types.BYTE_P; case SHORT: return Types.SHORT_P; case INT: return Types.INT; case LONG: return Types.LONG_P; case CHAR: return Types.CHAR; case FLOAT: return Types.FLOAT_P; case DOUBLE: return Types.DOUBLE_P; default: return null; } } @Override public Type visitNull(NullType nullType, Boolean p) { throw new IllegalStateException(); } @Override public Type visitArray(ArrayType arrayType, Boolean p) { if (arrayType.getComponentType() instanceof PrimitiveType) { Type type = getPrimitive((PrimitiveType) arrayType.getComponentType()); if (type != null) { return type.asArrayType(); } } return visit(arrayType.getComponentType(), p).asArrayType(); } @Override public Type visitDeclared(DeclaredType declaredType, Boolean p) { if (declaredType.asElement() instanceof TypeElement) { TypeElement typeElement = (TypeElement) declaredType.asElement(); switch (typeElement.getKind()) { case ENUM: return createEnumType(declaredType, typeElement, p); case ANNOTATION_TYPE: case CLASS: return createClassType(declaredType, typeElement, p); case INTERFACE: return createInterfaceType(declaredType, typeElement, p); default: throw new IllegalArgumentException("Illegal type " + typeElement); } } else { throw new IllegalArgumentException("Unsupported element type " + declaredType.asElement()); } } @Override public Type visitError(ErrorType errorType, Boolean p) { return visitDeclared(errorType, p); } @Override public Type visitTypeVariable(TypeVariable typeVariable, Boolean p) { String varName = typeVariable.toString(); if (typeVariable.getUpperBound() != null) { Type type = visit(typeVariable.getUpperBound(), p); return new TypeExtends(varName, type); } else if (typeVariable.getLowerBound() != null && !(typeVariable.getLowerBound() instanceof NullType)) { Type type = visit(typeVariable.getLowerBound(), p); return new TypeSuper(varName, type); } else { return null; } } @Override public Type visitWildcard(WildcardType wildcardType, Boolean p) { if (wildcardType.getExtendsBound() != null) { Type type = visit(wildcardType.getExtendsBound(), p); return new TypeExtends(type); } else if (wildcardType.getSuperBound() != null) { Type type = visit(wildcardType.getSuperBound(), p); return new TypeSuper(type); } else { return null; } } @Override public Type visitExecutable(ExecutableType t, Boolean p) { throw new IllegalStateException(); } @Override public Type visitNoType(NoType t, Boolean p) { return defaultType; } }; // TODO : return TypeMirror instead ?!? private final TypeVisitor<List<String>, Boolean> keyBuilder = new SimpleTypeVisitorAdapter<List<String>, Boolean>() { private final List<String> defaultValue = Collections.singletonList("Object"); private List<String> visitBase(TypeMirror t) { List<String> rv = new ArrayList<String>(); String name = t.toString(); if (name.contains("<")) { name = name.substring(0, name.indexOf('<')); } rv.add(name); return rv; } @Override public List<String> visitPrimitive(PrimitiveType t, Boolean p) { return Collections.singletonList(t.toString()); } @Override public List<String> visitNull(NullType t, Boolean p) { return defaultValue; } @Override public List<String> visitArray(ArrayType t, Boolean p) { List<String> rv = new ArrayList<String>(visit(t.getComponentType())); rv.add("[]"); return rv; } @Override public List<String> visitDeclared(DeclaredType t, Boolean p) { List<String> rv = visitBase(t); for (TypeMirror arg : t.getTypeArguments()) { if (p) { rv.addAll(visit(arg, false)); } else { rv.add(arg.toString()); } } return rv; } @Override public List<String> visitError(ErrorType t, Boolean p) { return visitDeclared(t, p); } @Override public List<String> visitTypeVariable(TypeVariable t, Boolean p) { List<String> rv = visitBase(t); if (t.getUpperBound() != null) { rv.addAll(visit(t.getUpperBound(), p)); } if (t.getLowerBound() != null) { rv.addAll(visit(t.getLowerBound(), p)); } return rv; } @Override public List<String> visitWildcard(WildcardType t, Boolean p) { List<String> rv = visitBase(t); if (t.getExtendsBound() != null) { rv.addAll(visit(t.getExtendsBound(), p)); } if (t.getSuperBound() != null) { rv.addAll(visit(t.getSuperBound(), p)); } return rv; } @Override public List<String> visitExecutable(ExecutableType t, Boolean p) { throw new IllegalStateException(); } @Override public List<String> visitNoType(NoType t, Boolean p) { return defaultValue; } }; public ExtendedTypeFactory( ProcessingEnvironment env, Set<Class<? extends Annotation>> annotations, TypeMappings typeMappings, QueryTypeFactory queryTypeFactory, Function<EntityType, String> variableNameFunction) { this.env = env; this.defaultType = new TypeExtends(Types.OBJECT); this.entityAnnotations = annotations; this.objectType = getErasedType(Object.class); this.numberType = getErasedType(Number.class); this.comparableType = getErasedType(Comparable.class); this.collectionType = getErasedType(Collection.class); this.listType = getErasedType(List.class); this.setType = getErasedType(Set.class); this.mapType = getErasedType(Map.class); this.typeMappings = typeMappings; this.queryTypeFactory = queryTypeFactory; this.variableNameFunction = variableNameFunction; } private TypeMirror getErasedType(Class<?> clazz) { return env.getTypeUtils().erasure(env.getElementUtils().getTypeElement(clazz.getName()).asType()); } private Type createType(TypeElement typeElement, TypeCategory category, List<? extends TypeMirror> typeArgs, boolean deep) { String name = typeElement.getQualifiedName().toString(); String simpleName = typeElement.getSimpleName().toString(); String packageName = env.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString(); Type[] params = new Type[typeArgs.size()]; for (int i = 0; i < params.length; i++) { params[i] = getType(typeArgs.get(i), deep); } return new SimpleType(category, name, packageName, simpleName, false, typeElement.getModifiers().contains(Modifier.FINAL), params); } public Collection<EntityType> getEntityTypes() { return entityTypeCache.values(); } @Nullable public Type getType(TypeMirror typeMirror, boolean deep) { List<String> key = keyBuilder.visit(typeMirror,true); if (entityTypeCache.containsKey(key)) { return entityTypeCache.get(key); } else if (typeCache.containsKey(key)) { return typeCache.get(key); } else { return createType(typeMirror, key, deep); } } @Nullable private Type createType(TypeMirror typeMirror, List<String> key, boolean deep) { typeCache.put(key, null); Type type = visitor.visit(typeMirror, deep); if (type != null && (type.getCategory() == TypeCategory.ENTITY || type.getCategory() == TypeCategory.CUSTOM)) { EntityType entityType = getEntityType(typeMirror, deep); typeCache.put(key, entityType); return entityType; } else { typeCache.put(key, type); return type; } } // TODO : simplify private Type createClassType(DeclaredType declaredType, TypeElement typeElement, boolean deep) { // other String name = typeElement.getQualifiedName().toString(); if (name.startsWith("java.")) { Iterator<? extends TypeMirror> i = declaredType.getTypeArguments().iterator(); if (isAssignable(declaredType, mapType)) { return createMapType(i, deep); } else if (isAssignable(declaredType, listType)) { return createCollectionType(Types.LIST, i, deep); } else if (isAssignable(declaredType, setType)) { return createCollectionType(Types.SET, i, deep); } else if (isAssignable(declaredType, collectionType)) { return createCollectionType(Types.COLLECTION, i, deep); } } TypeCategory typeCategory = TypeCategory.get(name); if (typeCategory != TypeCategory.NUMERIC && isAssignable(typeElement.asType(), comparableType) && isSubType(typeElement.asType(), numberType)) { typeCategory = TypeCategory.NUMERIC; } else if (!typeCategory.isSubCategoryOf(TypeCategory.COMPARABLE) && isAssignable(typeElement.asType(), comparableType)) { typeCategory = TypeCategory.COMPARABLE; } else if (typeCategory == TypeCategory.SIMPLE) { for (Class<? extends Annotation> entityAnn : entityAnnotations) { if (typeElement.getAnnotation(entityAnn) != null) { typeCategory = TypeCategory.ENTITY; } } } List<? extends TypeMirror> arguments = declaredType.getTypeArguments(); // for intersection types etc if (name.equals("")) { TypeMirror type = objectType; if (typeCategory == TypeCategory.COMPARABLE) { type = comparableType; } // find most specific type of superTypes which is a subtype of type List<? extends TypeMirror> superTypes = env.getTypeUtils().directSupertypes(declaredType); for (TypeMirror superType : superTypes) { if (env.getTypeUtils().isSubtype(superType, type)) { type = superType; } } typeElement = (TypeElement) env.getTypeUtils().asElement(type); if (type instanceof DeclaredType) { arguments = ((DeclaredType) type).getTypeArguments(); } } Type type = createType(typeElement, typeCategory, arguments, deep); TypeMirror superType = typeElement.getSuperclass(); TypeElement superTypeElement = null; if (superType instanceof DeclaredType) { superTypeElement = (TypeElement) ((DeclaredType) superType).asElement(); } // entity type for (Class<? extends Annotation> entityAnn : entityAnnotations) { if (typeElement.getAnnotation(entityAnn) != null || (superTypeElement != null && superTypeElement.getAnnotation(entityAnn) != null)) { EntityType entityType = new EntityType(type, variableNameFunction); typeMappings.register(entityType, queryTypeFactory.create(entityType)); return entityType; } } return type; } private Type createMapType(Iterator<? extends TypeMirror> typeMirrors, boolean deep) { if (!typeMirrors.hasNext()) { return new SimpleType(Types.MAP, defaultType, defaultType); } Type keyType = getType(typeMirrors.next(), deep); if (keyType == null) { keyType = defaultType; } Type valueType = getType(typeMirrors.next(), deep); if (valueType == null) { valueType = defaultType; } else if (valueType.getParameters().isEmpty()) { TypeElement element = env.getElementUtils().getTypeElement(valueType.getFullName()); if (element != null) { Type type = getType(element.asType(), deep); if (!type.getParameters().isEmpty()) { valueType = new SimpleType(valueType, new Type[type.getParameters().size()]); } } } return new SimpleType(Types.MAP, keyType, valueType); } private Type createCollectionType(Type baseType, Iterator<? extends TypeMirror> typeMirrors, boolean deep) { if (!typeMirrors.hasNext()) { return new SimpleType(baseType, defaultType); } Type componentType = getType(typeMirrors.next(), deep); if (componentType == null) { componentType = defaultType; } else if (componentType.getParameters().isEmpty()) { TypeElement element = env.getElementUtils().getTypeElement(componentType.getFullName()); if (element != null) { Type type = getType(element.asType(), deep); if (!type.getParameters().isEmpty()) { componentType = new SimpleType(componentType, new Type[type.getParameters().size()]); } } } return new SimpleType(baseType, componentType); } @Nullable public EntityType getEntityType(TypeMirror typeMirror, boolean deep) { List<String> key = keyBuilder.visit(typeMirror, true); // get from cache if (entityTypeCache.containsKey(key)) { EntityType entityType = entityTypeCache.get(key); if (deep && entityType.getSuperTypes().isEmpty()) { for (Type superType : getSupertypes(typeMirror, deep)) { entityType.addSupertype(new Supertype(superType)); } } return entityType; // create } else { return createEntityType(typeMirror, key, deep); } } @Nullable private EntityType createEntityType(TypeMirror typeMirror, List<String> key, boolean deep) { entityTypeCache.put(key, null); Type value = visitor.visit(typeMirror, deep); if (value != null) { EntityType entityType = null; if (value instanceof EntityType) { entityType = (EntityType) value; } else { entityType = new EntityType(value, variableNameFunction); typeMappings.register(entityType, queryTypeFactory.create(entityType)); } entityTypeCache.put(key, entityType); if (deep) { for (Type superType : getSupertypes(typeMirror, deep)) { entityType.addSupertype(new Supertype(superType)); } } return entityType; } else { return null; } } private Type createEnumType(DeclaredType declaredType, TypeElement typeElement, boolean deep) { // fallback Type enumType = createType(typeElement, TypeCategory.ENUM, declaredType.getTypeArguments(), deep); for (Class<? extends Annotation> entityAnn : entityAnnotations) { if (typeElement.getAnnotation(entityAnn) != null) { EntityType entityType = new EntityType(enumType, variableNameFunction); typeMappings.register(entityType, queryTypeFactory.create(entityType)); return entityType; } } return enumType; } private Type createInterfaceType(DeclaredType declaredType, TypeElement typeElement, boolean deep) { // entity type for (Class<? extends Annotation> entityAnn : entityAnnotations) { if (typeElement.getAnnotation(entityAnn) != null) { return createType(typeElement, TypeCategory.ENTITY, declaredType.getTypeArguments(), deep); } } Iterator<? extends TypeMirror> i = declaredType.getTypeArguments().iterator(); if (isAssignable(declaredType, mapType)) { return createMapType(i, deep); } else if (isAssignable(declaredType, listType)) { return createCollectionType(Types.LIST, i, deep); } else if (isAssignable(declaredType, setType)) { return createCollectionType(Types.SET, i, deep); } else if (isAssignable(declaredType, collectionType)) { return createCollectionType(Types.COLLECTION, i, deep); } else { String name = typeElement.getQualifiedName().toString(); return createType(typeElement, TypeCategory.get(name), declaredType.getTypeArguments(), deep); } } private Set<Type> getSupertypes(TypeMirror typeMirror, boolean deep) { boolean doubleIndex = doubleIndexEntities; doubleIndexEntities = false; Set<Type> superTypes = Collections.emptySet(); typeMirror = normalize(typeMirror); if (typeMirror.getKind() == TypeKind.DECLARED) { DeclaredType declaredType = (DeclaredType) typeMirror; TypeElement e = (TypeElement) declaredType.asElement(); // class if (e.getKind() == ElementKind.CLASS) { if (e.getSuperclass().getKind() != TypeKind.NONE) { TypeMirror supertype = normalize(e.getSuperclass()); if (supertype instanceof DeclaredType && ((DeclaredType) supertype).asElement().getAnnotation(QueryExclude.class) != null) { return Collections.emptySet(); } else { Type superClass = getType(supertype, deep); if (superClass == null) { System.err.println("Got no type for " + supertype); } else if (!superClass.getFullName().startsWith("java")) { superTypes = Collections.singleton(getType(supertype, deep)); } } } // interface } else { superTypes = new LinkedHashSet<Type>(e.getInterfaces().size()); for (TypeMirror mirror : e.getInterfaces()) { Type iface = getType(mirror, deep); if (!iface.getFullName().startsWith("java")) { superTypes.add(iface); } } } } else { return Collections.emptySet(); } doubleIndexEntities = doubleIndex; return superTypes; } private boolean isAssignable(TypeMirror type, TypeMirror iface) { return env.getTypeUtils().isAssignable(type, iface) // XXX Eclipse 3.6 support || env.getTypeUtils().erasure(type).toString().equals(iface.toString()); } private boolean isSubType(TypeMirror type1, TypeMirror clazz) { return env.getTypeUtils().isSubtype(type1, clazz) // XXX Eclipse 3.6 support || env.getTypeUtils().directSupertypes(type1).contains(clazz); } private TypeMirror normalize(TypeMirror type) { if (type.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVar = (TypeVariable) type; if (typeVar.getUpperBound() != null) { return typeVar.getUpperBound(); } } else if (type.getKind() == TypeKind.WILDCARD) { WildcardType wildcard = (WildcardType) type; if (wildcard.getExtendsBound() != null) { return wildcard.getExtendsBound(); } } return type; } public void extendTypes() { for (EntityType entityType : entityTypeCache.values()) { if (entityType.getProperties().isEmpty()) { for (Map.Entry<List<String>, EntityType> entry : entityTypeCache.entrySet()) { if (entry.getKey().get(0).equals(entityType.getFullName()) && !entry.getValue().getProperties().isEmpty()) { for (Property property : entry.getValue().getProperties()) { entityType.addProperty(property); } break; } } } } } }