diff --git a/.gitignore b/.gitignore index a95675f955..e786531d24 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ profiling/ *.iws .idea/ *uuid.state +querybean-generator/.factorypath +*.class diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml index 65d10a5d8c..b946930ed9 100644 --- a/querybean-generator/pom.xml +++ b/querybean-generator/pom.xml @@ -12,16 +12,63 @@ querybean-generator Querybean generator + + 1.36 + + + + + io.ebean + ebean-api + 14.8.1 + true + provided + + + io.ebean + jakarta-persistence-api + ${ebean-persistence-api.version} + true + provided + + + + org.jspecify + jspecify + 1.0.0 + true + provided + + + + io.avaje + avaje-prisms + ${avaje.prisms.version} + true + provided + + + + io.avaje + avaje-spi-service + 2.9 + + + + + io.avaje + junit + 1.5 + test + + - org.apache.maven.plugins maven-compiler-plugin 11 11 - - -proc:none diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java index 76949a8fd9..1927bd7673 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java @@ -25,7 +25,7 @@ interface Constants { String METAINF_MANIFEST = "META-INF/ebean-generated-info.mf"; String METAINF_SERVICES_MODULELOADER = "META-INF/services/io.ebean.config.EntityClassRegister"; - String AVAJE_LANG_NULLABLE = "io.avaje.lang.Nullable"; + String AVAJE_LANG_NULLABLE = "org.jspecify.annotations.Nullable"; String JAVA_COLLECTION = "java.util.Collection"; String EXPRESSIONLIST = "io.ebean.ExpressionList"; String EXPR = "io.ebean.Expr"; diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/FindDbName.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/FindDbName.java deleted file mode 100644 index 9fd0133823..0000000000 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/FindDbName.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.ebean.querybean.generator; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import java.util.List; -import java.util.Map; -import java.util.Set; - -class FindDbName { - - /** - * Return the value of the DbName annotation or null if it isn't found on the element. - */ - static String value(TypeElement element, Types typeUtils) { - - AnnotationMirror mirror = findDbNameMirror(element); - if (mirror != null) { - return readDbNameValue(mirror); - } - final TypeMirror typeMirror = element.getSuperclass(); - if (typeMirror.getKind() == TypeKind.NONE) { - return null; - } - final TypeElement element1 = (TypeElement)typeUtils.asElement(typeMirror); - return value(element1, typeUtils); - } - - private static String readDbNameValue(AnnotationMirror mirror) { - - final Map elementValues = mirror.getElementValues(); - final Set> entries = elementValues.entrySet(); - for (Map.Entry entry : entries) { - if ("value".equals(entry.getKey().getSimpleName().toString())) { - return (String) entry.getValue().getValue(); - } - } - return null; - } - - private static AnnotationMirror findDbNameMirror(TypeElement element) { - final List mirrors = element.getAnnotationMirrors(); - for (AnnotationMirror mirror : mirrors) { - final String name = mirror.getAnnotationType().asElement().toString(); - if (Constants.DBNAME.equals(name)) { - return mirror; - } - } - return null; - } -} diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/ModuleMeta.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/ModuleMeta.java deleted file mode 100644 index a1e98f3a19..0000000000 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/ModuleMeta.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.ebean.querybean.generator; - -import java.util.List; - -class ModuleMeta { - private final List entities; - private final List other; - - ModuleMeta(List entities, List other) { - this.entities = entities; - this.other = other; - } - - List getEntities() { - return entities; - } - - List getOther() { - return other; - } -} diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java index f149ecbb7c..c354e78280 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java @@ -1,8 +1,25 @@ package io.ebean.querybean.generator; -import javax.annotation.processing.Filer; +import static io.ebean.querybean.generator.APContext.filer; +import static io.ebean.querybean.generator.APContext.logError; +import static io.ebean.querybean.generator.APContext.logNote; +import static io.ebean.querybean.generator.APContext.typeElement; +import static io.ebean.querybean.generator.ProcessorUtils.trimAnnotations; + +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; + 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; @@ -16,118 +33,68 @@ 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. - */ +/** 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; + private static final ThreadLocal CTX = new ThreadLocal<>(); - /** - * All entity packages regardless of DB (for META-INF/ebean-generated-info.mf). - */ - private final Set allEntityPackages = new TreeSet<>(); + private static final class Ctx { + private final Set services = new TreeSet<>(); - private final Set otherClasses = new TreeSet<>(); + private final PropertyTypeMap propertyTypeMap = new PropertyTypeMap(); - /** - * The DB name prefixed entities. - */ - private final Set prefixEntities = new TreeSet<>(); + /** All entity packages regardless of DB (for META-INF/ebean-generated-info.mf). */ + private final Set allEntityPackages = new TreeSet<>(); - /** - * Entity classes for the default database. - */ - private final Set dbEntities = new TreeSet<>(); + private final Set otherClasses = new TreeSet<>(); - /** - * Entity classes for non default databases. - */ - private final Map> otherDbEntities = new TreeMap<>(); + /** The DB name prefixed entities. */ + private final Set prefixEntities = new TreeSet<>(); - /** - * All loaded entities regardless of db (to detect ones we add back from loadedPrefixEntities). - */ - private final Set loaded = new HashSet<>(); + /** Entity classes for the default database. */ + private final Set dbEntities = new TreeSet<>(); - /** - * For partial compile the previous list of prefixed entity classes. - */ - private final List loadedPrefixEntities = new ArrayList<>(); + /** Entity classes for non default databases. */ + private final Map> otherDbEntities = new TreeMap<>(); - /** - * The package for the generated EntityClassRegister. - */ - private String factoryPackage; + /** + * All loaded entities regardless of db (to detect ones we add back from loadedPrefixEntities). + */ + private final Set loaded = new HashSet<>(); - 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); - } + /** For partial compile the previous list of prefixed entity classes. */ + private final List loadedPrefixEntities = new ArrayList<>(); - TypeElement entityAnnotation() { - return elementUtils.getTypeElement(ENTITY); + /** The package for the generated EntityClassRegister. */ + private String factoryPackage; } - TypeElement embeddableAnnotation() { - return elementUtils.getTypeElement(EMBEDDABLE); + static void init(ProcessingEnvironment processingEnv) { + APContext.init(processingEnv); + CTX.set(new Ctx()); } - TypeElement converterAnnotation() { - return elementUtils.getTypeElement(CONVERTER); - } + static void clear() { + APContext.clear(); - TypeElement componentAnnotation() { - return elementUtils.getTypeElement(EBEAN_COMPONENT); + CTX.remove(); } - /** - * Gather all the fields (properties) for the given bean element. - */ - List allFields(Element element) { + /** Gather all the fields (properties) for the given bean element. */ + static 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) { + /** Recursively gather all the fields (properties) for the given bean element. */ + private static void gatherProperties(List fields, Element element) { TypeElement typeElement = (TypeElement) element; TypeMirror superclass = typeElement.getSuperclass(); - Element mappedSuper = typeUtils.asElement(superclass); + var mappedSuper = APContext.asTypeElement(superclass); if (isMappedSuperOrInheritance(mappedSuper)) { gatherProperties(fields, mappedSuper); } @@ -140,25 +107,21 @@ private void gatherProperties(List fields, Element element) { } } - /** - * Not interested in static, transient or Ebean internal fields. - */ - private boolean ignoreField(VariableElement field) { + /** Not interested in static, transient or Ebean internal fields. */ + private static boolean ignoreField(VariableElement field) { return isStaticOrTransient(field) || ignoreEbeanInternalFields(field); } - private boolean ignoreEbeanInternalFields(VariableElement field) { + private static boolean ignoreEbeanInternalFields(VariableElement field) { String fieldName = field.getSimpleName().toString(); return fieldName.startsWith("_ebean") || fieldName.startsWith("_EBEAN"); } - private boolean isStaticOrTransient(VariableElement field) { + private static boolean isStaticOrTransient(VariableElement field) { Set modifiers = field.getModifiers(); - return ( - modifiers.contains(Modifier.STATIC) || - modifiers.contains(Modifier.TRANSIENT) || - hasAnnotations(field, "jakarta.persistence.Transient") - ); + return (modifiers.contains(Modifier.STATIC) + || modifiers.contains(Modifier.TRANSIENT) + || hasAnnotations(field, "jakarta.persistence.Transient")); } private static boolean hasAnnotations(Element element, String... annotations) { @@ -180,50 +143,35 @@ private static AnnotationMirror getAnnotation(Element element, String... annotat return null; } - private boolean isMappedSuperOrInheritance(Element mappedSuper) { + private static boolean isMappedSuperOrInheritance(Element mappedSuper) { return hasAnnotations(mappedSuper, MAPPED_SUPERCLASS, INHERITANCE, DISCRIMINATOR_VALUE); } - private boolean isEntityOrEmbedded(Element mappedSuper) { - return hasAnnotations(mappedSuper, ENTITY, EMBEDDABLE); - } + private static boolean isEntityOrEmbedded(Element mappedSuper) { - boolean isEntity(Element element) { - return hasAnnotations(element, ENTITY); + return EntityPrism.isPresent(mappedSuper) || EmbeddablePrism.isPresent(mappedSuper); } - boolean isEmbeddable(Element element) { - return hasAnnotations(element, EMBEDDABLE); + static boolean isEmbeddable(Element element) { + return EmbeddablePrism.isPresent(element); } - /** - * Find the DbName annotation and return name if found. - */ - String findDbName(TypeElement element) { - return FindDbName.value(element, typeUtils); - } + /** Find the DbName annotation and return name if found. */ + static String findDbName(TypeElement element) { - /** - * Return true if it is a DbJson field. - */ - private static boolean dbJsonField(Element field) { - return hasAnnotations(field, DBJSON, DBJSONB); + return DbNamePrism.getOptionalOn(element).map(DbNamePrism::value).orElse(null); } - /** - * Return true if it is a DbArray field. - */ - private static boolean dbArrayField(Element field) { - return hasAnnotations(field, DBARRAY); + /** Return true if it is a DbJson field. */ + private static boolean dbJsonField(Element field) { + return DbJsonPrism.isPresent(field) || DbJsonBPrism.isPresent(field); } private static boolean dbToMany(Element field) { - return hasAnnotations(field, ONE_TO_MANY, MANY_TO_MANY); + return OneToManyPrism.isPresent(field) || ManyToManyPrism.isPresent(field); } - /** - * Escape the type (e.g. java.lang.String) from the TypeMirror toString(). - */ + /** 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; @@ -233,21 +181,12 @@ private static String typeDef(TypeMirror typeMirror) { } } - 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) { + static PropertyType getPropertyType(VariableElement field) { boolean toMany = dbToMany(field); if (dbJsonField(field)) { - return propertyTypeMap.getDbJsonType(); + return CTX.get().propertyTypeMap.getDbJsonType(); } - if (dbArrayField(field)) { + if (DbArrayPrism.isPresent(field)) { // get generic parameter type DeclaredType declaredType = (DeclaredType) field.asType(); String fullType = typeDef(declaredType.getTypeArguments().get(0)); @@ -256,23 +195,23 @@ PropertyType getPropertyType(VariableElement field) { final TypeMirror typeMirror = field.asType(); TypeMirror currentType = typeMirror; while (currentType != null) { - PropertyType type = propertyTypeMap.getType(typeDef(currentType)); + PropertyType type = CTX.get().propertyTypeMap.getType(typeDef(currentType)); if (type != null) { // simple scalar type return type; } // go up in class hierarchy - TypeElement fieldType = (TypeElement) typeUtils.asElement(currentType); + TypeElement fieldType = APContext.asTypeElement(currentType); currentType = (fieldType == null) ? null : fieldType.getSuperclass(); } - Element fieldType = typeUtils.asElement(typeMirror); + Element fieldType = APContext.asTypeElement(typeMirror); if (fieldType == null) { return null; } // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=544288 - fieldType = elementUtils.getTypeElement(fieldType.toString()); + fieldType = typeElement(fieldType.toString()); if (fieldType.getKind() == ElementKind.ENUM) { String fullType = typeDef(typeMirror); return new PropertyTypeEnum(fullType, Split.shortName(fullType)); @@ -281,7 +220,7 @@ PropertyType getPropertyType(VariableElement field) { // look for targetEntity annotation attribute final String targetEntity = readTargetEntity(field); if (targetEntity != null) { - final TypeElement element = elementUtils.getTypeElement(targetEntity); + final TypeElement element = typeElement(targetEntity); if (isEntityOrEmbedded(element)) { boolean embeddable = isEmbeddable(element); return createPropertyTypeAssoc(embeddable, toMany, typeDef(element.asType())); @@ -300,45 +239,27 @@ PropertyType getPropertyType(VariableElement field) { } else { result = null; } - if (result != null) { return result; + } else if (APContext.isAssignable(typeMirror.toString(), "java.lang.Comparable")) { + return new PropertyTypeScalarComparable(trimAnnotations(typeMirror.toString())); } 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 new PropertyTypeScalar(trimAnnotations(typeMirror.toString())); } - - return typeInstanceOf(typeElement.getSuperclass(), desiredInterface) || - typeElement - .getInterfaces() - .stream() - .anyMatch(t -> typeInstanceOf(t, desiredInterface)); } - private PropertyType createManyTypeAssoc(VariableElement field, DeclaredType declaredType) { + private static PropertyType createManyTypeAssoc( + VariableElement field, DeclaredType declaredType) { boolean toMany = dbToMany(field); List typeArguments = declaredType.getTypeArguments(); if (typeArguments.size() == 1) { - Element argElement = typeUtils.asElement(typeArguments.get(0)); + Element argElement = APContext.asTypeElement(typeArguments.get(0)); if (isEntityOrEmbedded(argElement)) { boolean embeddable = isEmbeddable(argElement); return createPropertyTypeAssoc(embeddable, toMany, typeDef(argElement.asType())); } } else if (typeArguments.size() == 2) { - Element argElement = typeUtils.asElement(typeArguments.get(1)); + Element argElement = APContext.asTypeElement(typeArguments.get(1)); if (isEntityOrEmbedded(argElement)) { boolean embeddable = isEmbeddable(argElement); return createPropertyTypeAssoc(embeddable, toMany, typeDef(argElement.asType())); @@ -347,7 +268,7 @@ private PropertyType createManyTypeAssoc(VariableElement field, DeclaredType dec return null; } - private String readTargetEntity(Element declaredType) { + private static String readTargetEntity(Element declaredType) { for (AnnotationMirror annotation : declaredType.getAnnotationMirrors()) { final Object targetEntity = readTargetEntityFromAnnotation(annotation); if (targetEntity != null) { @@ -358,7 +279,8 @@ private String readTargetEntity(Element declaredType) { } private static Object readTargetEntityFromAnnotation(AnnotationMirror mirror) { - for (Map.Entry entry : mirror.getElementValues().entrySet()) { + for (Map.Entry entry : + mirror.getElementValues().entrySet()) { if ("targetEntity".equals(entry.getKey().getSimpleName().toString())) { return entry.getValue().getValue(); } @@ -366,11 +288,10 @@ private static Object readTargetEntityFromAnnotation(AnnotationMirror mirror) { return null; } - /** - * Create the QAssoc PropertyType. - */ - private PropertyType createPropertyTypeAssoc(boolean embeddable, boolean toMany, String fullName) { - TypeElement typeElement = elementUtils.getTypeElement(fullName); + /** Create the QAssoc PropertyType. */ + private static PropertyType createPropertyTypeAssoc( + boolean embeddable, boolean toMany, String fullName) { + TypeElement typeElement = typeElement(fullName); String type; if (typeElement.getNestingKind().isNested()) { type = typeElement.getEnclosingElement().toString() + "$" + typeElement.getSimpleName(); @@ -378,77 +299,48 @@ private PropertyType createPropertyTypeAssoc(boolean embeddable, boolean toMany, type = typeElement.getQualifiedName().toString(); } - String suffix = toMany ? "Many" : embeddable ? "": "One"; + String suffix = toMany ? "Many" : embeddable ? "" : "One"; String[] split = Split.split(type); String propertyName = "Q" + split[1] + ".Assoc" + suffix; 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 logWarn(String msg, Object... args) { - messager.printMessage(Diagnostic.Kind.WARNING, String.format(msg, args)); - } - - void logNote(String msg, Object... args) { - messager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args)); - } - - void readModuleInfo() { + static void readModuleInfo() { String factory = loadMetaInfServices(); if (factory != null) { - TypeElement factoryType = elementUtils.getTypeElement(factory); + TypeElement factoryType = APContext.typeElement(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()); + var p = ModuleInfoPrism.getInstanceOn(factoryType); + CTX.get().loadedPrefixEntities.addAll(p.entities()); + CTX.get().otherClasses.addAll(p.other()); } } } - /** - * Register an entity with optional dbName. - */ - void addEntity(String beanFullName, String dbName) { - loaded.add(beanFullName); + /** Register an entity with optional dbName. */ + static void addEntity(String beanFullName, String dbName) { + CTX.get().loaded.add(beanFullName); final String pkg = packageOf(beanFullName); if (pkg != null) { - allEntityPackages.add(pkg); + CTX.get().allEntityPackages.add(pkg); updateFactoryPackage(pkg); } if (dbName != null) { - prefixEntities.add(dbName + ":" + beanFullName); - otherDbEntities.computeIfAbsent(dbName, s -> new TreeSet<>()).add(beanFullName); + CTX.get().prefixEntities.add(dbName + ":" + beanFullName); + CTX.get().otherDbEntities.computeIfAbsent(dbName, s -> new TreeSet<>()).add(beanFullName); } else { - prefixEntities.add(beanFullName); - dbEntities.add(beanFullName); + CTX.get().prefixEntities.add(beanFullName); + CTX.get().dbEntities.add(beanFullName); } } - /** - * Add back entity classes for partial compile. - */ - int complete() { + /** Add back entity classes for partial compile. */ + static int complete() { int added = 0; - for (String oldPrefixEntity : loadedPrefixEntities) { + for (String oldPrefixEntity : CTX.get().loadedPrefixEntities) { // maybe split as dbName:entityClass final String[] prefixEntityClass = oldPrefixEntity.split(":"); @@ -460,7 +352,7 @@ int complete() { } else { entityClass = prefixEntityClass[0]; } - if (!loaded.contains(entityClass)) { + if (!CTX.get().loaded.contains(entityClass)) { addEntity(entityClass, dbName); added++; } @@ -468,7 +360,7 @@ int complete() { return added; } - private String packageOf(String beanFullName) { + private static String packageOf(String beanFullName) { final int pos = beanFullName.lastIndexOf('.'); if (pos > -1) { return beanFullName.substring(0, pos); @@ -476,69 +368,70 @@ private String packageOf(String beanFullName) { return null; } - private void updateFactoryPackage(String pkg) { - if (pkg != null && (factoryPackage == null || factoryPackage.length() > pkg.length())) { - factoryPackage = pkg; + private static void updateFactoryPackage(String pkg) { + if (pkg != null + && (CTX.get().factoryPackage == null || CTX.get().factoryPackage.length() > pkg.length())) { + CTX.get().factoryPackage = pkg; } } - FileObject createMetaInfServicesWriter() throws IOException { + static FileObject createMetaInfServicesWriter() throws IOException { return createMetaInfWriter(METAINF_SERVICES_MODULELOADER); } - FileObject createManifestWriter() throws IOException { + static FileObject createManifestWriter() throws IOException { return createMetaInfWriter(METAINF_MANIFEST); } - FileObject createNativeImageWriter(String name) throws IOException { + static 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); + static FileObject createMetaInfWriter(String target) throws IOException { + return filer().createResource(StandardLocation.CLASS_OUTPUT, "", target); } - public boolean hasOtherClasses() { - return !otherClasses.isEmpty(); + static boolean hasOtherClasses() { + return !CTX.get().otherClasses.isEmpty(); } - public Set getOtherClasses() { - return otherClasses; + static Set getOtherClasses() { + return CTX.get().otherClasses; } - void addOther(Element element) { - otherClasses.add(element.toString()); + static void addOther(Element element) { + CTX.get().otherClasses.add(element.toString()); } - Set getPrefixEntities() { - return prefixEntities; + static Set getPrefixEntities() { + return CTX.get().prefixEntities; } - Set getDbEntities() { - return dbEntities; + static Set getDbEntities() { + return CTX.get().dbEntities; } - Map> getOtherDbEntities() { - return otherDbEntities; + static Map> getOtherDbEntities() { + return CTX.get().otherDbEntities; } - - Set getAllEntityPackages() { - return allEntityPackages; + static Set getAllEntityPackages() { + return CTX.get().allEntityPackages; } - String getFactoryPackage() { - return factoryPackage != null ? factoryPackage : "unknown"; + static String getFactoryPackage() { + return CTX.get().factoryPackage != null ? CTX.get().factoryPackage : "unknown"; } /** - * Return the class name of the generated EntityClassRegister - * (such that we can read the current metadata for partial compile). + * Return the class name of the generated EntityClassRegister (such that we can read the current + * metadata for partial compile). */ - String loadMetaInfServices() { + static String loadMetaInfServices() { try { - FileObject fileObject = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", METAINF_SERVICES_MODULELOADER); + FileObject fileObject = + filer().getResource(StandardLocation.CLASS_OUTPUT, "", METAINF_SERVICES_MODULELOADER); if (fileObject != null) { Reader reader = fileObject.openReader(true); LineNumberReader lineReader = new LineNumberReader(reader); @@ -551,18 +444,23 @@ String loadMetaInfServices() { } catch (FileNotFoundException | NoSuchFileException e) { // ignore - no services file yet } catch (FilerException e) { - logNote(null, "FilerException reading services file: " + e.getMessage()); + logNote("FilerException reading services file: " + e.getMessage()); } catch (Exception e) { - logError(null, "Error reading services file: " + e.getMessage()); + logError("Error reading services file: " + e.getMessage()); } return null; } - Element asElement(TypeMirror mirror) { - return typeUtils.asElement(mirror); + static boolean isNameClash(String shortName) { + return CTX.get().propertyTypeMap.isNameClash(shortName); + } + + static void validateModule() { + APContext.moduleInfoReader() + .ifPresent(r -> r.validateServices(AT_GENERATED, CTX.get().services)); } - boolean isNameClash(String shortName) { - return propertyTypeMap.isNameClash(shortName); + public static void addService(String factoryFullName) { + CTX.get().services.add(factoryFullName); } } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java index c32f3123d8..d90f973d3d 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java @@ -1,41 +1,46 @@ package io.ebean.querybean.generator; +import static io.ebean.querybean.generator.APContext.logError; +import static io.ebean.querybean.generator.APContext.logNote; +import static io.ebean.querybean.generator.APContext.logWarn; +import static io.ebean.querybean.generator.APContext.typeElement; + +import java.util.Arrays; +import java.util.Optional; +import java.util.Set; + import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.FilerException; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Set; -/** - * Process compiled entity beans and generates 'query beans' for them. - */ -public class Processor extends AbstractProcessor implements Constants { +import io.avaje.prism.GenerateAPContext; +import io.avaje.prism.GenerateModuleInfoReader; +import io.avaje.prism.GenerateUtils; - private ProcessingContext processingContext; +/** Process compiled entity beans and generates 'query beans' for them. */ +@GenerateUtils +@GenerateAPContext +@GenerateModuleInfoReader +@SupportedAnnotationTypes({ + ConverterPrism.PRISM_TYPE, + EbeanComponentPrism.PRISM_TYPE, + EntityPrism.PRISM_TYPE, + EmbeddablePrism.PRISM_TYPE, + ModuleInfoPrism.PRISM_TYPE +}) +public class Processor extends AbstractProcessor implements Constants { - public Processor() { - } + private SimpleModuleInfoWriter moduleWriter; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); - this.processingContext = new ProcessingContext(processingEnv); - } - - @Override - public Set getSupportedAnnotationTypes() { - Set annotations = new LinkedHashSet<>(); - annotations.add(ENTITY); - annotations.add(EMBEDDABLE); - annotations.add(CONVERTER); - annotations.add(EBEAN_COMPONENT); - annotations.add(MODULEINFO); - return annotations; + ProcessingContext.init(processingEnv); } @Override @@ -45,27 +50,33 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - processingContext.readModuleInfo(); + APContext.setProjectModuleElement(annotations, roundEnv); + ProcessingContext.readModuleInfo(); int count = processEntities(roundEnv); processOthers(roundEnv); - final int loaded = processingContext.complete(); + final int loaded = ProcessingContext.complete(); + initModuleInfoBean(); + if (count > 0) { + String msg = + "Ebean APT generated %s query beans, loaded %s others - META-INF/ebean-generated-info.mf entity-packages: %s"; + logNote(msg, count, loaded, ProcessingContext.getAllEntityPackages()); + } if (roundEnv.processingOver()) { writeModuleInfoBean(); - } - if (count > 0) { - String msg = "Ebean APT generated %s query beans, loaded %s others - META-INF/ebean-generated-info.mf entity-packages: %s"; - processingContext.logNote(msg, count, loaded, processingContext.getAllEntityPackages()); + ProcessingContext.validateModule(); + ProcessingContext.clear(); } return true; } private int processEntities(RoundEnvironment roundEnv) { int count = 0; - for (Element element : roundEnv.getElementsAnnotatedWith(processingContext.embeddableAnnotation())) { + + for (Element element : getElements(roundEnv, EmbeddablePrism.PRISM_TYPE)) { generateQueryBeans(element); count++; } - for (Element element : roundEnv.getElementsAnnotatedWith(processingContext.entityAnnotation())) { + for (Element element : getElements(roundEnv, EntityPrism.PRISM_TYPE)) { generateQueryBeans(element); count++; } @@ -73,35 +84,52 @@ private int processEntities(RoundEnvironment roundEnv) { } private void processOthers(RoundEnvironment round) { - processOthers(round, processingContext.converterAnnotation()); - processOthers(round, processingContext.componentAnnotation()); + getElements(round, ConverterPrism.PRISM_TYPE).forEach(ProcessingContext::addOther); + getElements(round, EbeanComponentPrism.PRISM_TYPE).forEach(ProcessingContext::addOther); } - private void processOthers(RoundEnvironment roundEnv, TypeElement otherType) { - if (otherType != null) { - for (Element element : roundEnv.getElementsAnnotatedWith(otherType)) { - processingContext.addOther(element); + private Set getElements(RoundEnvironment round, String name) { + return Optional.ofNullable(typeElement(name)) + .map(round::getElementsAnnotatedWith) + .orElse(Set.of()); + } + + private void initModuleInfoBean() { + try { + if (moduleWriter == null) { + moduleWriter = new SimpleModuleInfoWriter(); } + } catch (FilerException e) { + logWarn("FilerException trying to write EntityClassRegister: " + e); + } catch (Throwable e) { + logError("Failed to write EntityClassRegister error:" + e + " stack:" + Arrays.toString(e.getStackTrace())); } } private void writeModuleInfoBean() { try { - new SimpleModuleInfoWriter(processingContext).write(); - } catch (FilerException e) { - processingContext.logWarn(null, "FilerException trying to write EntityClassRegister: " + e); - } catch (Throwable e) { - processingContext.logError(null, "Failed to write EntityClassRegister error:" + e + " stack:" + Arrays.toString(e.getStackTrace())); + if (moduleWriter == null) { + logError("EntityClassRegister was not initialised and not written"); + } else { + moduleWriter.write(); + } + } catch (Exception e) { + e.printStackTrace(); + logError( + "Failed to write EntityClassRegister error:" + + e + + " stack:" + + Arrays.toString(e.getStackTrace())); } } private void generateQueryBeans(Element element) { try { - SimpleQueryBeanWriter beanWriter = new SimpleQueryBeanWriter((TypeElement) element, processingContext); + SimpleQueryBeanWriter beanWriter = + new SimpleQueryBeanWriter((TypeElement) element); beanWriter.writeRootBean(); - } catch (Throwable e) { - processingContext.logError(element, "Error generating query beans: " + e); + } catch (Exception e) { + logError(element, "Error generating query beans: " + e); } } - } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/ReadModuleInfo.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/ReadModuleInfo.java deleted file mode 100644 index 01f19a3044..0000000000 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/ReadModuleInfo.java +++ /dev/null @@ -1,69 +0,0 @@ -package io.ebean.querybean.generator; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Helper to read the current/existing prefixed entity classes. - *

- * These are added back on partial compile. - */ -class ReadModuleInfo { - - private final ProcessingContext ctx; - - public ReadModuleInfo(ProcessingContext ctx) { - this.ctx = ctx; - } - - ModuleMeta read(Element element) { - final List mirrors = element.getAnnotationMirrors(); - for (AnnotationMirror mirror : mirrors) { - final String name = mirror.getAnnotationType().asElement().toString(); - if (Constants.MODULEINFO.equals(name)) { - List entities = readEntities("entities", mirror); - List other = readEntities("other", mirror); - return new ModuleMeta(entities, other); - } - } - return null; - } - - @SuppressWarnings("unchecked") - private List readEntities(String key, AnnotationMirror mirror) { - final Map elementValues = mirror.getElementValues(); - final Set> entries = elementValues.entrySet(); - - for (Map.Entry entry : entries) { - if (key.equals(entry.getKey().getSimpleName().toString())) { - return readAttributes(entry.getValue()); - } - } - return Collections.emptyList(); - } - - @SuppressWarnings("unchecked") - private List readAttributes(AnnotationValue value) { - final Object entitiesValue = value.getValue(); - if (entitiesValue != null) { - try { - List vals = new ArrayList<>(); - List coll = (List) entitiesValue; - for (AnnotationValue annotationValue : coll) { - vals.add((String) annotationValue.getValue()); - } - return vals; - } catch (Exception e) { - ctx.logError(null, "Error reading ModuleInfo annotation, err " + e); - } - } - return Collections.emptyList(); - } -} diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java index 917e34b029..022e0b2a3c 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleModuleInfoWriter.java @@ -1,7 +1,7 @@ package io.ebean.querybean.generator; -import javax.tools.FileObject; -import javax.tools.JavaFileObject; +import static io.ebean.querybean.generator.APContext.logError; + import java.io.IOException; import java.io.Writer; import java.util.LinkedHashSet; @@ -9,26 +9,25 @@ import java.util.Set; import java.util.StringJoiner; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; + /** * Write the source code for the factory. */ class SimpleModuleInfoWriter { - private final ProcessingContext processingContext; - private final String factoryPackage; - private final String factoryShortName; + private final String factoryShortName= "EbeanEntityRegister"; private final String factoryFullName; private final JavaFileObject javaFileObject; private Append writer; - SimpleModuleInfoWriter(ProcessingContext processingContext) throws IOException { - this.processingContext = processingContext; - this.factoryPackage = processingContext.getFactoryPackage(); - this.factoryShortName = "EbeanEntityRegister"; + SimpleModuleInfoWriter() throws IOException { + this.factoryPackage = ProcessingContext.getFactoryPackage(); this.factoryFullName = factoryPackage + "." + factoryShortName; - this.javaFileObject = processingContext.createWriter(factoryFullName); + this.javaFileObject = APContext.createSourceFile(factoryFullName); } void write() throws IOException { @@ -44,22 +43,23 @@ void write() throws IOException { private void writeServicesFile() { try { - FileObject jfo = processingContext.createMetaInfServicesWriter(); + FileObject jfo = ProcessingContext.createMetaInfServicesWriter(); if (jfo != null) { Writer writer = jfo.openWriter(); + ProcessingContext.addService(factoryFullName); writer.write(factoryFullName); writer.close(); } } catch (IOException e) { - processingContext.logError(null, "Failed to write services file " + e.getMessage()); + logError("Failed to write services file " + e.getMessage()); } } private void writeManifestFile() { try { - final Set allEntityPackages = processingContext.getAllEntityPackages(); + final Set allEntityPackages = ProcessingContext.getAllEntityPackages(); if (!allEntityPackages.isEmpty()) { - FileObject jfo = processingContext.createManifestWriter(); + FileObject jfo = ProcessingContext.createManifestWriter(); if (jfo != null) { Writer writer = jfo.openWriter(); writer.write("generated-by: Ebean query bean generator\n"); @@ -69,7 +69,7 @@ private void writeManifestFile() { } } } catch (IOException e) { - processingContext.logError(null, "Failed to write services file " + e.getMessage()); + logError("Failed to write services file " + e.getMessage()); } } @@ -84,13 +84,13 @@ private String manifestEntityPackages(Set allEntityPackages) { private void writeNativeImageFile() { try { - Set allEntities = new LinkedHashSet<>(processingContext.getDbEntities()); - for (Set value : processingContext.getOtherDbEntities().values()) { + Set allEntities = new LinkedHashSet<>(ProcessingContext.getDbEntities()); + for (Set value : ProcessingContext.getOtherDbEntities().values()) { allEntities.addAll(value); } if (!allEntities.isEmpty()) { - FileObject jfo = processingContext.createNativeImageWriter(factoryPackage + ".ebean-entity"); + FileObject jfo = ProcessingContext.createNativeImageWriter(factoryPackage + ".ebean-entity"); if (jfo != null) { boolean first = true; Writer writer = jfo.openWriter(); @@ -111,7 +111,7 @@ private void writeNativeImageFile() { } } } catch (IOException e) { - processingContext.logError(null, "Failed to write services file " + e.getMessage()); + logError("Failed to write services file " + e.getMessage()); } } @@ -130,7 +130,7 @@ private void writePackage() { void buildAtContextModule(Append writer) { writer.append(Constants.AT_GENERATED).eol(); writer.append("@ModuleInfo("); - if (processingContext.hasOtherClasses()) { + if (ProcessingContext.hasOtherClasses()) { writer.append("other={%s}, ", otherClasses()); } writer.append("entities={%s}", prefixEntities()); @@ -138,11 +138,11 @@ void buildAtContextModule(Append writer) { } private String otherClasses() { - return quoteTypes(processingContext.getOtherClasses()); + return quoteTypes(ProcessingContext.getOtherClasses()); } private String prefixEntities() { - return quoteTypes(processingContext.getPrefixEntities()); + return quoteTypes(ProcessingContext.getPrefixEntities()); } private String quoteTypes(Set otherClasses) { @@ -158,9 +158,9 @@ private void writeStartClass() { writer.append("public class %s implements EntityClassRegister {", factoryShortName).eol().eol(); writeMethodOtherClasses(); - writeMethodEntityClasses(processingContext.getDbEntities(), null); + writeMethodEntityClasses(ProcessingContext.getDbEntities(), null); - final Map> otherDbEntities = processingContext.getOtherDbEntities(); + final Map> otherDbEntities = ProcessingContext.getOtherDbEntities(); for (Map.Entry> otherDb : otherDbEntities.entrySet()) { writeMethodEntityClasses(otherDb.getValue(), otherDb.getKey()); } @@ -171,11 +171,11 @@ private void writeStartClass() { private void writeMethodOtherClasses() { writeMethodComment("Register AttributeConverter etc", ""); writer.append(" private List> otherClasses() {").eol(); - if (!processingContext.hasOtherClasses()) { + if (!ProcessingContext.hasOtherClasses()) { writer.append(" return Collections.emptyList();").eol(); } else { writer.append(" List> others = new ArrayList<>();").eol(); - for (String otherType : processingContext.getOtherClasses()) { + for (String otherType : ProcessingContext.getOtherClasses()) { writer.append(" others.add(%s.class);", otherType).eol(); } writer.append(" return others;").eol(); @@ -192,14 +192,14 @@ private void writeMethodEntityClasses(Set dbEntities, String dbName) { writeMethodComment("Entities with no @DbName", dbName); } writer.append(" private List> %s() {", method).eol(); - if (dbEntities.isEmpty() && !processingContext.hasOtherClasses()) { + if (dbEntities.isEmpty() && !ProcessingContext.hasOtherClasses()) { writer.append(" return Collections.emptyList();").eol(); } else { writer.append(" List> entities = new ArrayList<>();").eol(); for (String dbEntity : dbEntities) { writer.append(" entities.add(%s.class);", dbEntity).eol(); } - if (processingContext.hasOtherClasses()) { + if (ProcessingContext.hasOtherClasses()) { writer.append(" entities.addAll(otherClasses());").eol(); } writer.append(" return entities;").eol(); diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java index c642146835..4f8887ac0e 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java @@ -1,10 +1,9 @@ package io.ebean.querybean.generator; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import javax.tools.JavaFileObject; +import static io.ebean.querybean.generator.APContext.asTypeElement; +import static io.ebean.querybean.generator.APContext.createSourceFile; + import java.io.IOException; import java.io.Writer; import java.util.ArrayList; @@ -12,6 +11,11 @@ import java.util.Set; import java.util.TreeSet; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.JavaFileObject; + /** * A simple implementation that generates and writes query beans. */ @@ -22,7 +26,6 @@ class SimpleQueryBeanWriter { private final TypeElement element; private final TypeElement implementsInterface; private String implementsInterfaceShortName; - private final ProcessingContext processingContext; private final String dbName; private final String beanFullName; private final boolean isEntity; @@ -34,25 +37,24 @@ class SimpleQueryBeanWriter { private final boolean fullyQualify; private Append writer; - SimpleQueryBeanWriter(TypeElement element, ProcessingContext processingContext) { + SimpleQueryBeanWriter(TypeElement element) { this.element = element; - this.processingContext = processingContext; this.beanFullName = element.getQualifiedName().toString(); boolean nested = element.getNestingKind().isNested(); this.destPackage = Util.packageOf(nested, beanFullName) + ".query"; String sn = Util.shortName(nested, beanFullName); this.shortInnerName = Util.shortName(false, sn); this.shortName = sn.replace('.', '$'); - this.isEntity = processingContext.isEntity(element); - this.embeddable = processingContext.isEmbeddable(element); + this.isEntity = EntityPrism.isPresent(element); + this.embeddable = ProcessingContext.isEmbeddable(element); this.dbName = findDbName(); this.implementsInterface = initInterface(element); - this.fullyQualify = processingContext.isNameClash(shortName); + this.fullyQualify = ProcessingContext.isNameClash(shortName); } private TypeElement initInterface(TypeElement element) { for (TypeMirror anInterface : element.getInterfaces()) { - TypeElement e = (TypeElement)processingContext.asElement(anInterface); + TypeElement e = asTypeElement(anInterface); String name = e.getQualifiedName().toString(); if (!name.startsWith("java") && !name.startsWith("io.ebean")) { return e; @@ -62,7 +64,7 @@ private TypeElement initInterface(TypeElement element) { } private String findDbName() { - return processingContext.findDbName(element); + return ProcessingContext.findDbName(element); } private boolean isEntity() { @@ -88,8 +90,8 @@ private void gatherPropertyDetails() { * Includes properties from mapped super classes and usual inheritance. */ private void addClassProperties() { - for (VariableElement field : processingContext.allFields(element)) { - PropertyType type = processingContext.getPropertyType(field); + for (VariableElement field : ProcessingContext.allFields(element)) { + PropertyType type = ProcessingContext.getPropertyType(field); if (type != null) { type.addImports(importTypes, fullyQualify); properties.add(new PropertyMeta(field.getSimpleName().toString(), type)); @@ -102,7 +104,7 @@ private void addClassProperties() { */ void writeRootBean() throws IOException { gatherPropertyDetails(); - processingContext.addEntity(beanFullName, dbName); + ProcessingContext.addEntity(beanFullName, dbName); writer = new Append(createFileWriter()); writePackage(); writeImports(); @@ -339,15 +341,13 @@ private void writeAssocBeanConstructor(String prefix) { } private void writeAssocBeanFetch() { - if (isEntity()) { - // inherit the fetch methods - if (implementsInterface != null) { - writeAssocBeanExpression(false, "eq", "Is equal to by ID property."); - writeAssocBeanExpression(true, "eqIfPresent", "Is equal to by ID property if the value is not null, if null no expression is added."); - writeAssocBeanExpression(false, "in", "IN the given values.", implementsInterfaceShortName + "...", "in"); - writeAssocBeanExpression(false, "inBy", "IN the given interface values.", "Collection", "in"); - writeAssocBeanExpression(true, "inOrEmptyBy", "IN the given interface values if the collection is not empty. No expression is added if the collection is empty..", "Collection", "inOrEmpty"); - } + // inherit the fetch methods + if (isEntity() && (implementsInterface != null)) { + writeAssocBeanExpression(false, "eq", "Is equal to by ID property."); + writeAssocBeanExpression(true, "eqIfPresent", "Is equal to by ID property if the value is not null, if null no expression is added."); + writeAssocBeanExpression(false, "in", "IN the given values.", implementsInterfaceShortName + "...", "in"); + writeAssocBeanExpression(false, "inBy", "IN the given interface values.", "Collection", "in"); + writeAssocBeanExpression(true, "inOrEmptyBy", "IN the given interface values if the collection is not empty. No expression is added if the collection is empty..", "Collection", "inOrEmpty"); } } @@ -358,7 +358,7 @@ private void writeAssocBeanExpression(boolean nullable,String expression, String private void writeAssocBeanExpression(boolean nullable, String expression, String comment, String param, String actualExpression) { final String nullableAnnotation = nullable ? "@Nullable " : ""; String values = expression.startsWith("in") ? "values" : "value"; - String castVarargs = expression.equals("in") ? "(Object[])" : ""; + String castVarargs = "in".equals(expression) ? "(Object[])" : ""; writer.append(" /**").eol(); writer.append(" * ").append(comment).eol(); writer.append(" */").eol(); @@ -388,7 +388,7 @@ private void writePackage() { } private Writer createFileWriter() throws IOException { - JavaFileObject jfo = processingContext.createWriter(destPackage + "." + "Q" + shortName, element); + JavaFileObject jfo = createSourceFile(destPackage + "." + "Q" + shortName, element); return jfo.openWriter(); } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/package-info.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/package-info.java new file mode 100644 index 0000000000..95cc2e7594 --- /dev/null +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/package-info.java @@ -0,0 +1,15 @@ +@GeneratePrism(io.ebean.annotation.DbArray.class) +@GeneratePrism(io.ebean.annotation.DbJson.class) +@GeneratePrism(io.ebean.annotation.DbJsonB.class) +@GeneratePrism(io.ebean.annotation.DbName.class) +@GeneratePrism(io.ebean.annotation.EbeanComponent.class) +@GeneratePrism(io.ebean.config.ModuleInfo.class) + +@GeneratePrism(jakarta.persistence.Converter.class) +@GeneratePrism(jakarta.persistence.Embeddable.class) +@GeneratePrism(jakarta.persistence.Entity.class) +@GeneratePrism(jakarta.persistence.ManyToMany.class) +@GeneratePrism(jakarta.persistence.OneToMany.class) +package io.ebean.querybean.generator; + +import io.avaje.prism.GeneratePrism; diff --git a/querybean-generator/src/main/java/module-info.java b/querybean-generator/src/main/java/module-info.java index f864e9528e..3bce8926e4 100644 --- a/querybean-generator/src/main/java/module-info.java +++ b/querybean-generator/src/main/java/module-info.java @@ -1,7 +1,13 @@ module io.ebean.querybean.generator { - provides javax.annotation.processing.Processor with io.ebean.querybean.generator.Processor; - requires java.compiler; requires java.sql; + + requires static io.avaje.prism; + requires static io.ebean.api; + requires static io.ebean.annotation; + requires static jakarta.persistence.api; + + provides javax.annotation.processing.Processor with io.ebean.querybean.generator.Processor; + } diff --git a/querybean-generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/querybean-generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 0ca63ab195..0000000000 --- a/querybean-generator/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -io.ebean.querybean.generator.Processor \ No newline at end of file diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/QueryBeanProcessorTest.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/QueryBeanProcessorTest.java new file mode 100644 index 0000000000..3c2ccbbf65 --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/QueryBeanProcessorTest.java @@ -0,0 +1,70 @@ +package io.ebean.querybean.generator; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class QueryBeanProcessorTest { + + @AfterEach + void deleteGeneratedFiles() throws Exception { + + Paths.get("ebean-generated-info.mf").toAbsolutePath().toFile().delete(); + Paths.get("io.ebean.config.EntityClassRegister").toAbsolutePath().toFile().delete(); + Paths.get("reflect-config.json").toAbsolutePath().toFile().delete(); + Files.walk(Paths.get("io").toAbsolutePath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + + // uncomment this to debug the processor for diagnosing issues + // @Test + void testGeneration() throws Exception { + final String source = + Paths.get("src/test/java/io/ebean/querybean/generator/entities") + .toAbsolutePath() + .toString(); + + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + final StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null); + + manager.setLocation(StandardLocation.SOURCE_PATH, List.of(new File(source))); + + final Set fileKinds = Set.of(Kind.SOURCE); + + final Iterable files = + manager.list(StandardLocation.SOURCE_PATH, "", fileKinds, true); + + final CompilationTask task = + compiler.getTask( + new PrintWriter(System.out), + null, + null, + List.of("--release=" + Integer.getInteger("java.specification.version")), + null, + files); + task.setProcessors(List.of(new Processor())); + + assertThat(task.call()).isTrue(); + } +} diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/ArticleEntity.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/ArticleEntity.java new file mode 100644 index 0000000000..7b95f3f46f --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/ArticleEntity.java @@ -0,0 +1,127 @@ +package io.ebean.querybean.generator.entities; + +import io.ebean.Model; +import io.ebean.annotation.WhenCreated; +import io.ebean.annotation.WhenModified; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "realworld.article") +public class ArticleEntity extends Model { + + @Id @GeneratedValue private UUID id; + + @WhenCreated + @Column(nullable = false, updatable = false) + private LocalDateTime createdAt; + + @WhenModified + @Column(nullable = false) + private LocalDateTime updatedAt; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "user_id", nullable = false) + private UserEntity author; + + @Column(nullable = false) + private String slug; + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private String description; + + @Column(nullable = false) + private String body; + + @OneToMany(mappedBy = "article", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + private final List tags = new ArrayList<>(); + + public UUID id() { + return id; + } + + public ArticleEntity id(UUID id) { + this.id = id; + return this; + } + + public UserEntity author() { + return author; + } + + public ArticleEntity author(UserEntity user) { + this.author = user; + return this; + } + + public String slug() { + return slug; + } + + public ArticleEntity slug(String slug) { + this.slug = slug; + return this; + } + + public String title() { + return title; + } + + public ArticleEntity title(String title) { + this.title = title; + return this; + } + + public String description() { + return description; + } + + public ArticleEntity description(String description) { + this.description = description; + return this; + } + + public String body() { + return body; + } + + public ArticleEntity body(String body) { + this.body = body; + return this; + } + + public List tags() { + return tags; + } + + public LocalDateTime createdAt() { + return createdAt; + } + + public void createdAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime updatedAt() { + return updatedAt; + } + + public void updatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/ArticleTags.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/ArticleTags.java new file mode 100644 index 0000000000..058fe04cdf --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/ArticleTags.java @@ -0,0 +1,72 @@ +package io.ebean.querybean.generator.entities; + +import java.util.UUID; + +import io.ebean.Model; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; + +@Entity +@Table(name = "realworld.article_tag") +public class ArticleTags extends Model { + + @EmbeddedId private ArticleTagId id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "article_id", nullable = false, insertable = false, updatable = false) + private final ArticleEntity article; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "tag_id", nullable = false, insertable = false, updatable = false) + private final TagEntity tag; + + public ArticleTags(ArticleEntity article, TagEntity tag) { + this.article = article; + this.tag = tag; + } + + public ArticleEntity article() { + return article; + } + + public TagEntity tag() { + return tag; + } + + public ArticleTagId id() { + return id; + } + + public void id(ArticleTagId id) { + this.id = id; + } + + @Embeddable + public static class ArticleTagId { + UUID articleId; + UUID tagId; + + public ArticleTagId(UUID articleId, UUID tagId) {} + + public UUID getArticleId() { + return articleId; + } + + public void setArticleId(UUID articleId) { + this.articleId = articleId; + } + + public UUID getTagId() { + return tagId; + } + + public void setTagId(UUID tagId) { + this.tagId = tagId; + } + } +} diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/CommentEntity.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/CommentEntity.java new file mode 100644 index 0000000000..a3c1a785d0 --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/CommentEntity.java @@ -0,0 +1,52 @@ +package io.ebean.querybean.generator.entities; + +import io.ebean.Model; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.Table; + +@Entity +@Table(name = "comments") +@NamedEntityGraph(name = "fetch-author", attributeNodes = @NamedAttributeNode("author")) +public class CommentEntity extends Model { + + @Column(nullable = false) + private String body; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id", nullable = false) + private UserEntity author; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "article_id", nullable = false) + private ArticleEntity article; + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public UserEntity getAuthor() { + return author; + } + + public void setAuthor(UserEntity author) { + this.author = author; + } + + public ArticleEntity getArticle() { + return article; + } + + public void setArticle(ArticleEntity article) { + this.article = article; + } +} diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/FavoriteEntity.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/FavoriteEntity.java new file mode 100644 index 0000000000..c49283e91c --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/FavoriteEntity.java @@ -0,0 +1,40 @@ +package io.ebean.querybean.generator.entities; + +import io.ebean.Model; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.util.UUID; + +@Entity +@Table(name = "favorites") +public class FavoriteEntity extends Model { + + @Id UUID id; + + @ManyToOne(fetch = FetchType.LAZY) + private ArticleEntity article; + + @ManyToOne(fetch = FetchType.LAZY) + private UserEntity user; + + public ArticleEntity article() { + return article; + } + + public FavoriteEntity article(ArticleEntity article) { + this.article = article; + return this; + } + + public UserEntity user() { + return user; + } + + public FavoriteEntity user(UserEntity user) { + this.user = user; + return this; + } +} diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/TagEntity.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/TagEntity.java new file mode 100644 index 0000000000..dc06b59a23 --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/TagEntity.java @@ -0,0 +1,27 @@ +package io.ebean.querybean.generator.entities; + +import io.ebean.Model; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; + +@Entity +@Table(name = "realworld.tag") +public class TagEntity extends Model { + @Id private final UUID id; + private final String name; + + public TagEntity(UUID id, String name) { + this.id = id; + this.name = name; + } + + public UUID id() { + return id; + } + + public String name() { + return name; + } +} diff --git a/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/UserEntity.java b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/UserEntity.java new file mode 100644 index 0000000000..daa1cca30f --- /dev/null +++ b/querybean-generator/src/test/java/io/ebean/querybean/generator/entities/UserEntity.java @@ -0,0 +1,86 @@ +package io.ebean.querybean.generator.entities; + +import io.ebean.Model; +import io.ebean.annotation.Encrypted; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; + +@Entity +@Table(name = "realworld.user") +public class UserEntity extends Model { + + @Id @GeneratedValue private UUID id; + + @Column(nullable = false, unique = true) + private String username; + + @Encrypted + @Column(nullable = false) + private String passwordHash; + + @Column(nullable = false, unique = true) + private String email; + + @Column(nullable = false) + private String bio; + + private String image; + + public UUID id() { + return id; + } + + public UserEntity id(UUID id) { + this.id = id; + return this; + } + + public String username() { + return username; + } + + public UserEntity username(String username) { + this.username = username; + return this; + } + + public String passwordHash() { + return passwordHash; + } + + public UserEntity passwordHash(String passwordHash) { + this.passwordHash = passwordHash; + return this; + } + + public String email() { + return email; + } + + public UserEntity email(String email) { + this.email = email; + return this; + } + + public String bio() { + return bio; + } + + public UserEntity bio(String bio) { + this.bio = bio; + return this; + } + + public String image() { + return image; + } + + public UserEntity image(String image) { + this.image = image; + return this; + } +}