io.ebean.querybean.generator.ProcessingContext Maven / Gradle / Ivy
package io.ebean.querybean.generator;
import javax.annotation.processing.Filer;
import javax.annotation.processing.FilerException;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
/**
* Context for the source generation.
*/
class ProcessingContext implements Constants {
private final ProcessingEnvironment processingEnv;
private final Types typeUtils;
private final Filer filer;
private final Messager messager;
private final Elements elementUtils;
private final PropertyTypeMap propertyTypeMap = new PropertyTypeMap();
private final ReadModuleInfo readModuleInfo;
/**
* All entity packages regardless of DB (for META-INF/ebean-generated-info.mf).
*/
private final Set allEntityPackages = new TreeSet<>();
private final Set otherClasses = new TreeSet<>();
/**
* The DB name prefixed entities.
*/
private final Set prefixEntities = new TreeSet<>();
/**
* Entity classes for the default database.
*/
private final Set dbEntities = new TreeSet<>();
/**
* Entity classes for non default databases.
*/
private final Map> otherDbEntities = new TreeMap<>();
/**
* All loaded entities regardless of db (to detect ones we add back from loadedPrefixEntities).
*/
private final Set loaded = new HashSet<>();
/**
* For partial compile the previous list of prefixed entity classes.
*/
private final List loadedPrefixEntities = new ArrayList<>();
/**
* The package for the generated EntityClassRegister.
*/
private String factoryPackage;
ProcessingContext(ProcessingEnvironment processingEnv) {
this.processingEnv = processingEnv;
this.typeUtils = processingEnv.getTypeUtils();
this.filer = processingEnv.getFiler();
this.messager = processingEnv.getMessager();
this.elementUtils = processingEnv.getElementUtils();
this.readModuleInfo = new ReadModuleInfo(this);
}
TypeElement entityAnnotation() {
return elementUtils.getTypeElement(ENTITY);
}
TypeElement embeddableAnnotation() {
return elementUtils.getTypeElement(EMBEDDABLE);
}
TypeElement converterAnnotation() {
return elementUtils.getTypeElement(CONVERTER);
}
TypeElement componentAnnotation() {
return elementUtils.getTypeElement(EBEAN_COMPONENT);
}
/**
* Gather all the fields (properties) for the given bean element.
*/
List allFields(Element element) {
List list = new ArrayList<>();
gatherProperties(list, element);
return list;
}
/**
* Recursively gather all the fields (properties) for the given bean element.
*/
private void gatherProperties(List fields, Element element) {
TypeElement typeElement = (TypeElement) element;
TypeMirror superclass = typeElement.getSuperclass();
Element mappedSuper = typeUtils.asElement(superclass);
if (isMappedSuperOrInheritance(mappedSuper)) {
gatherProperties(fields, mappedSuper);
}
List allFields = ElementFilter.fieldsIn(element.getEnclosedElements());
for (VariableElement field : allFields) {
if (!ignoreField(field)) {
fields.add(field);
}
}
}
/**
* Not interested in static, transient or Ebean internal fields.
*/
private boolean ignoreField(VariableElement field) {
return isStaticOrTransient(field) || ignoreEbeanInternalFields(field);
}
private boolean ignoreEbeanInternalFields(VariableElement field) {
String fieldName = field.getSimpleName().toString();
return fieldName.startsWith("_ebean") || fieldName.startsWith("_EBEAN");
}
private boolean isStaticOrTransient(VariableElement field) {
Set modifiers = field.getModifiers();
return (
modifiers.contains(Modifier.STATIC) ||
modifiers.contains(Modifier.TRANSIENT) ||
hasAnnotations(field, "jakarta.persistence.Transient")
);
}
private static boolean hasAnnotations(Element element, String... annotations) {
return getAnnotation(element, annotations) != null;
}
private static AnnotationMirror getAnnotation(Element element, String... annotations) {
if (element == null) {
return null;
}
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
final String name = annotationMirror.getAnnotationType().asElement().toString();
for (String annotation : annotations) {
if (annotation.equals(name)) {
return annotationMirror;
}
}
}
return null;
}
private boolean isMappedSuperOrInheritance(Element mappedSuper) {
return hasAnnotations(mappedSuper, MAPPED_SUPERCLASS, INHERITANCE, DISCRIMINATOR_VALUE);
}
private boolean isEntityOrEmbedded(Element mappedSuper) {
return hasAnnotations(mappedSuper, ENTITY, EMBEDDABLE);
}
boolean isEntity(Element element) {
return hasAnnotations(element, ENTITY);
}
boolean isEmbeddable(Element element) {
return hasAnnotations(element, EMBEDDABLE);
}
/**
* Find the DbName annotation and return name if found.
*/
String findDbName(TypeElement element) {
return FindDbName.value(element, typeUtils);
}
/**
* Return true if it is a DbJson field.
*/
private static boolean dbJsonField(Element field) {
return hasAnnotations(field, DBJSON, DBJSONB);
}
/**
* Return true if it is a DbArray field.
*/
private static boolean dbArrayField(Element field) {
return hasAnnotations(field, DBARRAY);
}
/**
* Escape the type (e.g. java.lang.String) from the TypeMirror toString().
*/
private static String typeDef(TypeMirror typeMirror) {
if (typeMirror.getKind() == TypeKind.DECLARED) {
DeclaredType declaredType = (DeclaredType) typeMirror;
return declaredType.asElement().toString();
} else {
return typeMirror.toString();
}
}
private String trimAnnotations(String type) {
int pos = type.indexOf("@");
if (pos == -1) {
return type;
}
String remainder = type.substring(0, pos) + type.substring(type.indexOf(' ') + 1);
return trimAnnotations(remainder);
}
PropertyType getPropertyType(VariableElement field) {
if (dbJsonField(field)) {
return propertyTypeMap.getDbJsonType();
}
if (dbArrayField(field)) {
// get generic parameter type
DeclaredType declaredType = (DeclaredType) field.asType();
String fullType = typeDef(declaredType.getTypeArguments().get(0));
return new PropertyTypeArray(fullType, Split.shortName(fullType));
}
final TypeMirror typeMirror = field.asType();
TypeMirror currentType = typeMirror;
while (currentType != null) {
PropertyType type = propertyTypeMap.getType(typeDef(currentType));
if (type != null) {
// simple scalar type
return type;
}
// go up in class hierarchy
TypeElement fieldType = (TypeElement) typeUtils.asElement(currentType);
currentType = (fieldType == null) ? null : fieldType.getSuperclass();
}
Element fieldType = typeUtils.asElement(typeMirror);
if (fieldType == null) {
return null;
}
// workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=544288
fieldType = elementUtils.getTypeElement(fieldType.toString());
if (fieldType.getKind() == ElementKind.ENUM) {
String fullType = typeDef(typeMirror);
return new PropertyTypeEnum(fullType, Split.shortName(fullType));
}
// look for targetEntity annotation attribute
final String targetEntity = readTargetEntity(field);
if (targetEntity != null) {
final TypeElement element = elementUtils.getTypeElement(targetEntity);
if (isEntityOrEmbedded(element)) {
return createPropertyTypeAssoc(typeDef(element.asType()));
}
}
if (isEntityOrEmbedded(fieldType)) {
// public QAssocContact contacts;
return createPropertyTypeAssoc(typeDef(typeMirror));
}
final PropertyType result;
if (typeMirror.getKind() == TypeKind.DECLARED) {
result = createManyTypeAssoc(field, (DeclaredType) typeMirror);
} else {
result = null;
}
if (result != null) {
return result;
} else {
if (typeInstanceOf(typeMirror, "java.lang.Comparable")) {
return new PropertyTypeScalarComparable(trimAnnotations(typeMirror.toString()));
} else {
return new PropertyTypeScalar(trimAnnotations(typeMirror.toString()));
}
}
}
private boolean typeInstanceOf(final TypeMirror typeMirror, final CharSequence desiredInterface) {
TypeElement typeElement = (TypeElement) typeUtils.asElement(typeMirror);
if (typeElement == null || typeElement.getQualifiedName().contentEquals("java.lang.Object")) {
return false;
}
if (typeElement.getQualifiedName().contentEquals(desiredInterface)) {
return true;
}
return typeInstanceOf(typeElement.getSuperclass(), desiredInterface) ||
typeElement
.getInterfaces()
.stream()
.anyMatch(t -> typeInstanceOf(t, desiredInterface));
}
private PropertyType createManyTypeAssoc(VariableElement field, DeclaredType declaredType) {
List extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments.size() == 1) {
Element argElement = typeUtils.asElement(typeArguments.get(0));
if (isEntityOrEmbedded(argElement)) {
return createPropertyTypeAssoc(typeDef(argElement.asType()));
}
} else if (typeArguments.size() == 2) {
Element argElement = typeUtils.asElement(typeArguments.get(1));
if (isEntityOrEmbedded(argElement)) {
return createPropertyTypeAssoc(typeDef(argElement.asType()));
}
}
return null;
}
private String readTargetEntity(Element declaredType) {
for (AnnotationMirror annotation : declaredType.getAnnotationMirrors()) {
final Object targetEntity = readTargetEntityFromAnnotation(annotation);
if (targetEntity != null) {
return targetEntity.toString();
}
}
return null;
}
private static Object readTargetEntityFromAnnotation(AnnotationMirror mirror) {
for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry : mirror.getElementValues().entrySet()) {
if ("targetEntity".equals(entry.getKey().getSimpleName().toString())) {
return entry.getValue().getValue();
}
}
return null;
}
/**
* Create the QAssoc PropertyType.
*/
private PropertyType createPropertyTypeAssoc(String fullName) {
TypeElement typeElement = elementUtils.getTypeElement(fullName);
String type;
if (typeElement.getNestingKind().isNested()) {
type = typeElement.getEnclosingElement().toString() + "$" + typeElement.getSimpleName();
} else {
type = typeElement.getQualifiedName().toString();
}
String[] split = Split.split(type);
String propertyName = "Q" + split[1] + ".Assoc";
String importName = split[0] + ".query.Q" + split[1];
return new PropertyTypeAssoc(propertyName, importName);
}
/**
* Create a file writer for the given class name.
*/
JavaFileObject createWriter(String factoryClassName, Element originatingElement) throws IOException {
return filer.createSourceFile(factoryClassName, originatingElement);
}
/**
* Create a file writer for the given class name without an originating element.
*/
JavaFileObject createWriter(String factoryClassName) throws IOException {
return filer.createSourceFile(factoryClassName);
}
void logError(Element e, String msg, Object... args) {
messager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), e);
}
void logNote(String msg, Object... args) {
messager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
}
void readModuleInfo() {
String factory = loadMetaInfServices();
if (factory != null) {
TypeElement factoryType = elementUtils.getTypeElement(factory);
if (factoryType != null) {
// previous prefixed entities to add back for partial compile
final ModuleMeta read = readModuleInfo.read(factoryType);
loadedPrefixEntities.addAll(read.getEntities());
otherClasses.addAll(read.getOther());
}
}
}
/**
* Register an entity with optional dbName.
*/
void addEntity(String beanFullName, String dbName) {
loaded.add(beanFullName);
final String pkg = packageOf(beanFullName);
if (pkg != null) {
allEntityPackages.add(pkg);
updateFactoryPackage(pkg);
}
if (dbName != null) {
prefixEntities.add(dbName + ":" + beanFullName);
otherDbEntities.computeIfAbsent(dbName, s -> new TreeSet<>()).add(beanFullName);
} else {
prefixEntities.add(beanFullName);
dbEntities.add(beanFullName);
}
}
/**
* Add back entity classes for partial compile.
*/
int complete() {
int added = 0;
for (String oldPrefixEntity : loadedPrefixEntities) {
// maybe split as dbName:entityClass
final String[] prefixEntityClass = oldPrefixEntity.split(":");
String dbName = null;
String entityClass;
if (prefixEntityClass.length > 1) {
dbName = prefixEntityClass[0];
entityClass = prefixEntityClass[1];
} else {
entityClass = prefixEntityClass[0];
}
if (!loaded.contains(entityClass)) {
addEntity(entityClass, dbName);
added++;
}
}
return added;
}
private String packageOf(String beanFullName) {
final int pos = beanFullName.lastIndexOf('.');
if (pos > -1) {
return beanFullName.substring(0, pos);
}
return null;
}
private void updateFactoryPackage(String pkg) {
if (pkg != null && (factoryPackage == null || factoryPackage.length() > pkg.length())) {
factoryPackage = pkg;
}
}
FileObject createMetaInfServicesWriter() throws IOException {
return createMetaInfWriter(METAINF_SERVICES_MODULELOADER);
}
FileObject createManifestWriter() throws IOException {
return createMetaInfWriter(METAINF_MANIFEST);
}
FileObject createNativeImageWriter(String name) throws IOException {
String nm = "META-INF/native-image/" + name + "/reflect-config.json";
return createMetaInfWriter(nm);
}
FileObject createMetaInfWriter(String target) throws IOException {
return filer.createResource(StandardLocation.CLASS_OUTPUT, "", target);
}
public boolean hasOtherClasses() {
return !otherClasses.isEmpty();
}
public Set getOtherClasses() {
return otherClasses;
}
void addOther(Element element) {
otherClasses.add(element.toString());
}
Set getPrefixEntities() {
return prefixEntities;
}
Set getDbEntities() {
return dbEntities;
}
Map> getOtherDbEntities() {
return otherDbEntities;
}
Set getAllEntityPackages() {
return allEntityPackages;
}
String getFactoryPackage() {
return factoryPackage != null ? factoryPackage : "unknown";
}
/**
* Return the class name of the generated EntityClassRegister
* (such that we can read the current metadata for partial compile).
*/
String loadMetaInfServices() {
try {
FileObject fileObject = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", METAINF_SERVICES_MODULELOADER);
if (fileObject != null) {
Reader reader = fileObject.openReader(true);
LineNumberReader lineReader = new LineNumberReader(reader);
String line = lineReader.readLine();
if (line != null) {
return line.trim();
}
}
} catch (FileNotFoundException | NoSuchFileException e) {
// ignore - no services file yet
} catch (FilerException e) {
logNote(null, "FilerException reading services file: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
logError(null, "Error reading services file: " + e.getMessage());
}
return null;
}
Element asElement(TypeMirror mirror) {
return typeUtils.asElement(mirror);
}
boolean isNameClash(String shortName) {
return propertyTypeMap.isNameClash(shortName);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy