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

com.querydsl.codegen.GenericExporter Maven / Gradle / Ivy

/*
 * 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 com.querydsl.codegen.utils.JavaWriter;
import com.querydsl.codegen.utils.ScalaWriter;
import com.querydsl.codegen.utils.model.Parameter;
import com.querydsl.codegen.utils.model.Type;
import com.querydsl.codegen.utils.model.TypeCategory;
import com.querydsl.codegen.utils.support.ClassUtils;
import com.querydsl.core.QueryException;
import com.querydsl.core.annotations.Config;
import com.querydsl.core.annotations.PropertyType;
import com.querydsl.core.annotations.QueryEmbeddable;
import com.querydsl.core.annotations.QueryEmbedded;
import com.querydsl.core.annotations.QueryEntity;
import com.querydsl.core.annotations.QueryExclude;
import com.querydsl.core.annotations.QueryInit;
import com.querydsl.core.annotations.QueryProjection;
import com.querydsl.core.annotations.QuerySupertype;
import com.querydsl.core.annotations.QueryTransient;
import com.querydsl.core.annotations.QueryType;
import com.querydsl.core.util.Annotations;
import com.querydsl.core.util.BeanUtils;
import com.querydsl.core.util.ReflectionUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;

/**
 * {@code GenericExporter} provides query type serialization logic for cases where APT annotation
 * processors can't be used. {@code GenericExporter} scans the classpath for classes annotated with
 * specified annotations in specific packages and mirrors them into Querydsl expression types.
 *
 * 

Example with Querydsl annotations: * *

{@code
 * GenericExporter exporter = new GenericExporter();
 * exporter.setTargetFolder(new File("target/generated-sources/java"));
 * exporter.export(com.example.domain.Entity.class.getPackage());
 * }
* *

Example with JPA annotations: * *

{@code
 * GenericExporter exporter = new GenericExporter();
 * exporter.setKeywords(Keywords.JPA);
 * exporter.setEntityAnnotation(Entity.class);
 * exporter.setEmbeddableAnnotation(Embeddable.class);
 * exporter.setEmbeddedAnnotation(Embedded.class);
 * exporter.setSupertypeAnnotation(MappedSuperclass.class);
 * exporter.setSkipAnnotation(Transient.class);
 * exporter.setTargetFolder(new File("target/generated-sources/java"));
 * exporter.export(com.example.domain.Entity.class.getPackage());
 * }
* * @author tiwe */ public class GenericExporter { private Class entityAnnotation = QueryEntity.class; private Class supertypeAnnotation = QuerySupertype.class; private Class embeddableAnnotation = QueryEmbeddable.class; private Class embeddedAnnotation = QueryEmbedded.class; private Class skipAnnotation = QueryTransient.class; private boolean createScalaSources = false; private final Set> stopClasses = new HashSet<>(); private final Map allTypes = new HashMap<>(); private final Map, EntityType> entityTypes = new HashMap<>(); private final Map, EntityType> superTypes = new HashMap<>(); private final Map, EntityType> embeddableTypes = new HashMap<>(); private final Map, EntityType> projectionTypes = new HashMap<>(); private final CodegenModule codegenModule = new CodegenModule(); private SerializerConfig serializerConfig = SimpleSerializerConfig.DEFAULT; @Deprecated private boolean handleFields = true, handleMethods = true; private PropertyHandling propertyHandling = PropertyHandling.ALL; private boolean useFieldTypes = false; @Nullable private File targetFolder; @Nullable private TypeFactory typeFactory; private final List annotationHelpers = new ArrayList<>(); @Nullable private TypeMappings typeMappings; @Nullable private QueryTypeFactory queryTypeFactory; @Nullable private Class serializerClass; private final Charset charset; private final ClassLoader classLoader; private Set generatedFiles = new HashSet<>(); private boolean strictMode; /** * Create a GenericExporter instance using the given classloader and charset for serializing * source files * * @param classLoader classloader to use * @param charset charset of target sources */ public GenericExporter(ClassLoader classLoader, Charset charset) { this.classLoader = classLoader; this.charset = charset; stopClasses.add(Object.class); stopClasses.add(Enum.class); } /** * Create a GenericExporter instance using the given classloader and default charset * * @param classLoader classloader to use */ public GenericExporter(ClassLoader classLoader) { this(classLoader, Charset.defaultCharset()); } /** * Create a GenericExporter instance using the context classloader and the given charset * * @param charset charset of target sources */ public GenericExporter(Charset charset) { this(Thread.currentThread().getContextClassLoader(), charset); } /** Create a GenericExporter instance using the context classloader and default charset */ public GenericExporter() { this(Thread.currentThread().getContextClassLoader(), Charset.defaultCharset()); } /** * Export the given packages * * @param packages packages to be scanned */ public void export(Package... packages) { var pkgs = new String[packages.length]; for (var i = 0; i < packages.length; i++) { pkgs[i] = packages[i].getName(); } export(pkgs); } /** * Export the given packages * * @param packages packages to be scanned */ public void export(String... packages) { scanPackages(packages); innerExport(); } /** * Export the given classes * * @param classes classes to be scanned */ public void export(Class... classes) { for (Class cl : classes) { handleClass(cl); } innerExport(); } @SuppressWarnings("unchecked") private void innerExport() { typeMappings = codegenModule.get(TypeMappings.class); queryTypeFactory = codegenModule.get(QueryTypeFactory.class); typeFactory = new TypeFactory( Arrays.asList(entityAnnotation, supertypeAnnotation, embeddableAnnotation), codegenModule.get(Function.class, CodegenModule.VARIABLE_NAME_FUNCTION_CLASS)); // copy annotations helpers to typeFactory for (AnnotationHelper helper : annotationHelpers) { typeFactory.addAnnotationHelper(helper); } // process supertypes for (Class cl : superTypes.keySet()) { createEntityType(cl, superTypes); } // process embeddables for (Class cl : embeddableTypes.keySet()) { createEntityType(cl, embeddableTypes); } // process entities for (Class cl : entityTypes.keySet()) { createEntityType(cl, entityTypes); } // process projections for (Class cl : projectionTypes.keySet()) { createEntityType(cl, projectionTypes); } // add constructors and properties for (Map, EntityType> entries : Arrays.asList(superTypes, embeddableTypes, entityTypes, projectionTypes)) { for (Map.Entry, EntityType> entry : new HashSet<>(entries.entrySet())) { addConstructors(entry.getKey(), entry.getValue()); addProperties(entry.getKey(), entry.getValue()); } } // merge supertype fields into subtypes Set handled = new HashSet<>(); for (EntityType type : superTypes.values()) { addSupertypeFields(type, allTypes, handled); } for (EntityType type : entityTypes.values()) { addSupertypeFields(type, allTypes, handled); } for (EntityType type : embeddableTypes.values()) { addSupertypeFields(type, allTypes, handled); } // extend types typeFactory.extendTypes(); try { Serializer supertypeSerializer, entitySerializer, embeddableSerializer, projectionSerializer; if (serializerClass != null) { Serializer serializer = codegenModule.get(serializerClass); supertypeSerializer = serializer; entitySerializer = serializer; embeddableSerializer = serializer; projectionSerializer = serializer; } else { supertypeSerializer = codegenModule.get(SupertypeSerializer.class); entitySerializer = codegenModule.get(EntitySerializer.class); embeddableSerializer = codegenModule.get(EmbeddableSerializer.class); projectionSerializer = codegenModule.get(ProjectionSerializer.class); } // serialize super types serialize(supertypeSerializer, superTypes); // serialize entity types serialize(entitySerializer, entityTypes); // serialize embeddable types serialize(embeddableSerializer, embeddableTypes); // serialize projection types serialize(projectionSerializer, projectionTypes); } catch (IOException e) { throw new QueryException(e); } } private void addSupertypeFields( EntityType model, Map superTypes, Set handled) { if (handled.add(model)) { for (Supertype supertype : model.getSuperTypes()) { var entityType = superTypes.get(supertype.getType().getFullName()); if (entityType == null) { if (supertype.getType().getPackageName().startsWith("java.")) { // skip internal supertypes continue; } // FIXME this misses the generics Class cl = supertype.getType().getJavaClass(); typeFactory.addEmbeddableType(cl); entityType = createEntityType(cl, new HashMap<>()); addProperties(cl, entityType); } addSupertypeFields(entityType, superTypes, handled); supertype.setEntityType(entityType); model.include(supertype); } } } private boolean containsAny(Class clazz, Class... annotations) { for (Class annType : annotations) { if (clazz.isAnnotationPresent(annType)) { return true; } } return false; } private EntityType createEntityType(Class cl, Map, EntityType> types) { if (types.get(cl) != null) { return types.get(cl); } else { var type = allTypes.get(ClassUtils.getFullName(cl)); if (type == null) { type = typeFactory.getEntityType(cl); } types.put(cl, type); var fullName = ClassUtils.getFullName(cl); if (!allTypes.containsKey(fullName)) { allTypes.put(fullName, type); } typeMappings.register(type, queryTypeFactory.create(type)); if (strictMode && cl.getSuperclass() != null) { @SuppressWarnings("unchecked") var annotations = (Class[]) new Class[] {entityAnnotation, supertypeAnnotation, embeddableAnnotation}; if (!containsAny(cl.getSuperclass(), annotations)) { // skip supertype handling return type; } } if (cl.getSuperclass() != null && !stopClasses.contains(cl.getSuperclass()) && !cl.getSuperclass().isAnnotationPresent(QueryExclude.class)) { type.addSupertype( new Supertype(typeFactory.get(cl.getSuperclass(), cl.getGenericSuperclass()))); } if (cl.isInterface()) { for (Class iface : cl.getInterfaces()) { if (!stopClasses.contains(iface) && !iface.isAnnotationPresent(QueryExclude.class)) { type.addSupertype(new Supertype(typeFactory.get(iface))); } } } return type; } } private void addConstructors(Class cl, EntityType type) { final var constructors = cl.getConstructors(); Arrays.sort(constructors, Comparator.comparing(Constructor::toString)); for (Constructor constructor : constructors) { if (constructor.isAnnotationPresent(QueryProjection.class)) { List parameters = new ArrayList<>(); for (var i = 0; i < constructor.getParameterTypes().length; i++) { var parameterType = typeFactory.get( constructor.getParameterTypes()[i], constructor.getGenericParameterTypes()[i]); for (Annotation annotation : constructor.getParameterAnnotations()[i]) { if (annotation.annotationType().equals(QueryType.class)) { var queryType = (QueryType) annotation; parameterType = parameterType.as(TypeCategory.valueOf(queryType.value().name())); } } parameters.add(new Parameter("param" + i, parameterType)); } type.addConstructor(new com.querydsl.codegen.utils.model.Constructor(parameters)); } } } private void addProperties(Class cl, EntityType type) { Map types = new HashMap<>(); Map annotations = new HashMap<>(); var config = propertyHandling.getConfig(cl); // fields if (config.isFields()) { for (Field field : cl.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers())) { if (Modifier.isTransient(field.getModifiers()) && !field.isAnnotationPresent(QueryType.class)) { continue; } var annotated = ReflectionUtils.getAnnotatedElement(cl, field.getName(), field.getType()); var propertyType = getPropertyType(cl, annotated, field.getType(), field.getGenericType()); var ann = new Annotations(field); types.put(field.getName(), propertyType); annotations.put(field.getName(), ann); } } } // getters if (config.isMethods()) { for (Method method : cl.getDeclaredMethods()) { var name = method.getName(); if (method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers()) && !method.isBridge() && ((name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2))) { String propertyName; if (name.startsWith("get")) { propertyName = BeanUtils.uncapitalize(name.substring(3)); } else { propertyName = BeanUtils.uncapitalize(name.substring(2)); } var propertyType = getPropertyType(cl, method, method.getReturnType(), method.getGenericReturnType()); if (!types.containsKey(propertyName) || !useFieldTypes) { types.put(propertyName, propertyType); } var ann = annotations.get(propertyName); if (ann == null) { ann = new Annotations(); annotations.put(propertyName, ann); } ann.addAnnotations(method); } } } for (Map.Entry entry : types.entrySet()) { var ann = annotations.get(entry.getKey()); Property property = createProperty(type, entry.getKey(), entry.getValue(), ann); if (property != null) { type.addProperty(property); } } } private Type getPropertyType( Class cl, AnnotatedElement annotated, Class type, java.lang.reflect.Type genericType) { Type propertyType = null; if (annotated.isAnnotationPresent(embeddedAnnotation)) { Class embeddableType = type; if (Collection.class.isAssignableFrom(type)) { embeddableType = ReflectionUtils.getTypeParameterAsClass(genericType, 0); } else if (Map.class.isAssignableFrom(type)) { embeddableType = ReflectionUtils.getTypeParameterAsClass(genericType, 1); } if (!embeddableType.getName().startsWith("java.")) { typeFactory.addEmbeddableType(embeddableType); if (!embeddableTypes.containsKey(embeddableType) && !entityTypes.containsKey(embeddableType) && !superTypes.containsKey(embeddableType)) { var entityType = createEntityType(embeddableType, embeddableTypes); addProperties(embeddableType, entityType); if (embeddableType == type) { propertyType = entityType; } } } } if (propertyType == null) { propertyType = typeFactory.get(type, annotated, genericType); if (propertyType instanceof EntityType && !allTypes.containsKey(ClassUtils.getFullName(type))) { var fullName = ClassUtils.getFullName(type); if (!allTypes.containsKey(fullName)) { allTypes.put(fullName, (EntityType) propertyType); } } } return propertyType; } @Nullable private Property createProperty( EntityType entityType, String propertyName, Type propertyType, AnnotatedElement annotated) { List inits = Collections.emptyList(); if (annotated.isAnnotationPresent(skipAnnotation) && !annotated.isAnnotationPresent(QueryType.class)) { return null; } if (annotated.isAnnotationPresent(QueryInit.class)) { inits = Arrays.asList(annotated.getAnnotation(QueryInit.class).value()); } if (annotated.isAnnotationPresent(QueryType.class)) { var queryType = annotated.getAnnotation(QueryType.class); if (queryType.value().equals(PropertyType.NONE)) { return null; } propertyType = propertyType.as(TypeCategory.valueOf(queryType.value().name())); } return new Property(entityType, propertyName, propertyType, inits); } private void scanPackages(String... packages) { if (packages == null) { return; } for (String pkg : packages) { try { for (Class cl : ClassPathUtils.scanPackage(classLoader, pkg)) { handleClass(cl); } } catch (IOException e) { throw new QueryException(e); } } } private void handleClass(Class cl) { if (stopClasses.contains(cl) || cl.isAnnotationPresent(QueryExclude.class)) { return; } else if (cl.isAnnotationPresent(entityAnnotation)) { entityTypes.put(cl, null); } else if (cl.isAnnotationPresent(embeddableAnnotation)) { embeddableTypes.put(cl, null); } else if (cl.isAnnotationPresent(supertypeAnnotation)) { superTypes.put(cl, null); } else { for (Constructor constructor : cl.getConstructors()) { if (constructor.isAnnotationPresent(QueryProjection.class)) { projectionTypes.put(cl, null); break; } } } } private void serialize(Serializer serializer, Map, EntityType> types) throws IOException { for (Map.Entry, EntityType> entityType : types.entrySet()) { var type = typeMappings.getPathType(entityType.getValue(), entityType.getValue(), true); var packageName = type.getPackageName(); var className = packageName.length() > 0 ? (packageName + "." + type.getSimpleName()) : type.getSimpleName(); var config = serializerConfig; if (entityType.getKey().isAnnotationPresent(Config.class)) { config = SimpleSerializerConfig.getConfig(entityType.getKey().getAnnotation(Config.class)); } var fileSuffix = createScalaSources ? ".scala" : ".java"; write(serializer, className.replace('.', '/') + fileSuffix, config, entityType.getValue()); } } private void write( Serializer serializer, String path, SerializerConfig serializerConfig, EntityType type) throws IOException { var targetFile = new File(targetFolder, path); generatedFiles.add(targetFile); try (var w = writerFor(targetFile)) { var writer = createScalaSources ? new ScalaWriter(w) : new JavaWriter(w); serializer.serialize(type, serializerConfig, writer); } } private Writer writerFor(File file) { if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { System.err.println("Folder " + file.getParent() + " could not be created"); } try { return new OutputStreamWriter(new FileOutputStream(file), charset); } catch (FileNotFoundException e) { throw new RuntimeException(e.getMessage(), e); } } /** * Return the set of generated files * * @return a set of generated files */ public Set getGeneratedFiles() { return generatedFiles; } /** * Set the entity annotation * * @param entityAnnotation entity annotation */ public void setEntityAnnotation(Class entityAnnotation) { this.entityAnnotation = entityAnnotation; } /** * Set the supertype annotation * * @param supertypeAnnotation supertype annotation */ public void setSupertypeAnnotation(Class supertypeAnnotation) { this.supertypeAnnotation = supertypeAnnotation; } /** * Set the embeddable annotation * * @param embeddableAnnotation embeddable annotation */ public void setEmbeddableAnnotation(Class embeddableAnnotation) { this.embeddableAnnotation = embeddableAnnotation; } /** * Set the embedded annotation * * @param embeddedAnnotation embedded annotation */ public void setEmbeddedAnnotation(Class embeddedAnnotation) { this.embeddedAnnotation = embeddedAnnotation; } /** * Set the skip annotation * * @param skipAnnotation skip annotation */ public void setSkipAnnotation(Class skipAnnotation) { this.skipAnnotation = skipAnnotation; } /** * Set the target folder for generated sources * * @param targetFolder */ public void setTargetFolder(File targetFolder) { this.targetFolder = targetFolder; } /** * Set the serializer class to be used * * @param serializerClass */ public void setSerializerClass(Class serializerClass) { codegenModule.bind(serializerClass); this.serializerClass = serializerClass; } /** * Set the typemappings class to be used * * @param typeMappingsClass */ public void setTypeMappingsClass(Class typeMappingsClass) { codegenModule.bind(TypeMappings.class, typeMappingsClass); } /** * Set whether Scala sources are generated * * @param createScalaSources */ public void setCreateScalaSources(boolean createScalaSources) { this.createScalaSources = createScalaSources; } /** * Set the keywords to be used * * @param keywords */ public void setKeywords(Collection keywords) { codegenModule.bind(CodegenModule.KEYWORDS, keywords); } /** * Set the name prefix * * @param prefix */ public void setNamePrefix(String prefix) { codegenModule.bind(CodegenModule.PREFIX, prefix); } /** * Set the name suffix * * @param suffix */ public void setNameSuffix(String suffix) { codegenModule.bind(CodegenModule.SUFFIX, suffix); } /** * Set the package suffix * * @param suffix */ public void setPackageSuffix(String suffix) { codegenModule.bind(CodegenModule.PACKAGE_SUFFIX, suffix); } /** * Set whether fields are handled (default true) * * @param b * @deprecated Use {@link #setPropertyHandling(PropertyHandling)} instead */ @Deprecated public void setHandleFields(boolean b) { handleFields = b; setPropertyHandling(); } /** * Set whether methods are handled (default true) * * @param b * @deprecated Use {@link #setPropertyHandling(PropertyHandling)} instead */ @Deprecated public void setHandleMethods(boolean b) { handleMethods = b; setPropertyHandling(); } private void setPropertyHandling() { if (handleFields) { propertyHandling = handleMethods ? PropertyHandling.ALL : PropertyHandling.FIELDS; } else if (handleMethods) { propertyHandling = PropertyHandling.METHODS; } else { propertyHandling = PropertyHandling.NONE; } } /** * Set the property handling mode * * @param propertyHandling */ public void setPropertyHandling(PropertyHandling propertyHandling) { this.propertyHandling = propertyHandling; } /** * Set whether field types should be used instead of getter return types (default false) * * @param b */ public void setUseFieldTypes(boolean b) { useFieldTypes = b; } /** * Add a stop class to be used (default Object.class and Enum.class) * * @param cl */ public void addStopClass(Class cl) { stopClasses.add(cl); } /** * Set whether annotationless superclasses are handled or not (default: true) * * @param s */ public void setStrictMode(boolean s) { strictMode = s; } /** * Set the serializer configuration to use * * @param serializerConfig */ public void setSerializerConfig(SerializerConfig serializerConfig) { this.serializerConfig = serializerConfig; } /** * Add a annotation helper object to process custom annotations * * @param annotationHelper */ public void addAnnotationHelper(AnnotationHelper annotationHelper) { annotationHelpers.add(annotationHelper); } /** * Set the Generated annotation class. Will default to java {@code @Generated} * * @param generatedAnnotationClass the fully qualified class name of the Single-Element * Annotation (with {@code String} element) to be used on the generated sources, or * {@code null} (defaulting to {@code javax.annotation.Generated} or {@code * javax.annotation.processing.Generated} depending on the java version). * @see Single-Element * Annotation */ public void setGeneratedAnnotationClass(@Nullable String generatedAnnotationClass) { codegenModule.bindInstance( CodegenModule.GENERATED_ANNOTATION_CLASS, GeneratedAnnotationResolver.resolve(generatedAnnotationClass)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy