Skip to content

Commit

Permalink
Add javax.Generated annotation to classes generated by the java-gener…
Browse files Browse the repository at this point in the history
…ator
  • Loading branch information
andreaTP committed Sep 23, 2022
1 parent 4856950 commit b05049b
Show file tree
Hide file tree
Showing 19 changed files with 147 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -21,6 +21,7 @@

#### New Features
* Fix #4398: add annotation @PreserveUnknownFields for marking generated field have `x-kubernetes-preserve-unknown-fields: true` defined
* Fix #4351: add `javax.annotation.processing.Generated` to classes generated with the `java-generator`

#### _**Note**_: Breaking changes in the API
* Fix #4350: SchemaSwap's fieldName parameter now expects a field name only, not a method or a constructor.
Expand Down
Expand Up @@ -29,23 +29,25 @@
import io.sundr.model.TypeDef;
import io.sundr.model.repo.DefinitionRepository;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;

import javax.annotation.processing.*;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;

@SupportedAnnotationTypes({"io.fabric8.kubernetes.model.annotation.Version"})
@SupportedAnnotationTypes({ "io.fabric8.kubernetes.model.annotation.Version" })
public class CustomResourceAnnotationProcessor extends AbstractProcessor {

private final CRDGenerator generator = new CRDGenerator();

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Expand All @@ -54,7 +56,8 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
final CRDGenerationInfo allCRDs = generator.withOutput(new FileObjectCRDOutput(processingEnv)).detailedGenerate();
allCRDs.getCRDDetailsPerNameAndVersion().forEach((crdName, versionToInfo) -> {
messager.printMessage(Diagnostic.Kind.NOTE, "Generating CRD " + crdName + ":\n");
versionToInfo.forEach((version, info) -> messager.printMessage(Diagnostic.Kind.NOTE, " - " + version + " -> " + info.getFilePath()));
versionToInfo.forEach(
(version, info) -> messager.printMessage(Diagnostic.Kind.NOTE, " - " + version + " -> " + info.getFilePath()));
});
return true;
}
Expand All @@ -66,18 +69,27 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element instanceof TypeElement) {
try {
// The annotation is loaded with reflection for compatibility with Java 8
Class<Annotation> generatedAnnotation = (Class<Annotation>) Class.forName("javax.annotation.processing.Generated");
if (element.getAnnotationsByType(generatedAnnotation).length > 0) {
continue;
}
} catch (ClassNotFoundException e) {
// ignore
}
generator.customResources(toCustomResourceInfo((TypeElement) element));
}
}
}

return false;
}

private CustomResourceInfo toCustomResourceInfo(TypeElement customResource) {
TypeDef definition = Adapters.adaptType(customResource, AptContext.getContext());
definition = Types.unshallow(definition);

if (CustomResourceInfo.DESCRIBE_TYPE_DEFS) {
Types.output(definition);
}
Expand All @@ -86,37 +98,38 @@ private CustomResourceInfo toCustomResourceInfo(TypeElement customResource) {
SpecAndStatus specAndStatus = Types.resolveSpecAndStatusTypes(definition);
if (specAndStatus.isUnreliable()) {
System.out.println("Cannot reliably determine status types for " + crClassName
+ " because it isn't parameterized with only spec and status types. Status replicas detection will be deactivated.");
+ " because it isn't parameterized with only spec and status types. Status replicas detection will be deactivated.");
}

final String group = customResource.getAnnotation(Group.class).value();
final String version = customResource.getAnnotation(Version.class).value();

final String kind = Optional.ofNullable(customResource.getAnnotation(Kind.class))
.map(Kind::value)
.orElse(customResource.getSimpleName().toString());
.map(Kind::value)
.orElse(customResource.getSimpleName().toString());

final String singular = Optional.ofNullable(customResource.getAnnotation(Singular.class))
.map(Singular::value)
.orElse(kind.toLowerCase(Locale.ROOT));
.map(Singular::value)
.orElse(kind.toLowerCase(Locale.ROOT));

final String plural = Optional.ofNullable(customResource.getAnnotation(Plural.class))
.map(Plural::value)
.map(s -> s.toLowerCase(Locale.ROOT))
.orElse(Pluralize.toPlural(singular));
.map(Plural::value)
.map(s -> s.toLowerCase(Locale.ROOT))
.orElse(Pluralize.toPlural(singular));

final String[] shortNames = Optional
.ofNullable(customResource.getAnnotation(ShortNames.class))
.map(ShortNames::value)
.orElse(new String[]{});
.ofNullable(customResource.getAnnotation(ShortNames.class))
.map(ShortNames::value)
.orElse(new String[] {});

final boolean storage = customResource.getAnnotation(Version.class).storage();
final boolean served = customResource.getAnnotation(Version.class).served();

final Scope scope = Types.isNamespaced(definition) ? Scope.NAMESPACED : Scope.CLUSTER;

return new CustomResourceInfo(group, version, kind, singular, plural, shortNames, storage, served, scope, definition, crClassName.toString(),
specAndStatus.getSpecClassName(), specAndStatus.getStatusClassName());
return new CustomResourceInfo(group, version, kind, singular, plural, shortNames, storage, served, scope, definition,
crClassName.toString(),
specAndStatus.getSpecClassName(), specAndStatus.getStatusClassName());
}

private static class FileObjectOutputStream extends OutputStream {
Expand Down Expand Up @@ -170,8 +183,8 @@ public FileObjectCRDOutput(ProcessingEnvironment processingEnv) {
@Override
protected FileObjectOutputStream createStreamFor(String crdName) throws IOException {
return new FileObjectOutputStream(
processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "",
"META-INF/fabric8/" + crdName + ".yml"));
processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "",
"META-INF/fabric8/" + crdName + ".yml"));
}

@Override
Expand All @@ -180,4 +193,3 @@ public URI crdURI(String crdName) {
}
}
}

Expand Up @@ -55,6 +55,10 @@ public class GenerateJavaSources implements Runnable {
"--code-structure" }, description = "Generate classes using a specific layout", required = false, hidden = true)
String codeStructure = null;

@Option(names = { "-skip-generated-annotations",
"--skip-generated-annotations" }, description = "Add extra lombok and sundrio annotation to the generated classes", required = false, hidden = true)
Boolean skipGeneratedAnnotations = null;

@Override
public void run() {
final Config.Prefix pSt = (prefixStrategy != null) ? Config.Prefix.valueOf(prefixStrategy) : null;
Expand All @@ -66,7 +70,8 @@ public void run() {
sSt,
alwaysPreserveUnkownFields,
addExtraAnnotations,
structure);
structure,
!skipGeneratedAnnotations);
final CRGeneratorRunner runner = new CRGeneratorRunner(config);
runner.run(source, target);
}
Expand Down
Expand Up @@ -39,13 +39,15 @@ public enum Suffix {
private static final boolean DEFAULT_ALWAYS_PRESERVE_FIELDS = false;
private static final boolean DEFAULT_ADD_EXTRA_ANNOTATIONS = false;
private static final CodeStructure DEFAULT_CODE_STRUCTURE = CodeStructure.PACKAGE_NESTED;
private static final boolean DEFAULT_ADD_GENERATED_ANNOTATIONS = true;

private Boolean uppercaseEnums = DEFAULT_UPPERCASE_ENUM;
private Prefix prefixStrategy = DEFAULT_PREFIX_STRATEGY;
private Suffix suffixStrategy = DEFAULT_SUFFIX_STRATEGY;
private Boolean alwaysPreserveUnknownFields = DEFAULT_ALWAYS_PRESERVE_FIELDS;
private Boolean objectExtraAnnotations = DEFAULT_ADD_EXTRA_ANNOTATIONS;
private CodeStructure structure = DEFAULT_CODE_STRUCTURE;
private Boolean generatedAnnotations = DEFAULT_ADD_GENERATED_ANNOTATIONS;

public Config() {
}
Expand All @@ -56,7 +58,8 @@ public Config(
Suffix suffixStrategy,
Boolean alwaysPreserveUnknownFields,
Boolean objectExtraAnnotations,
CodeStructure structure) {
CodeStructure structure,
Boolean generatedAnnotations) {
if (uppercaseEnums != null) {
this.uppercaseEnums = uppercaseEnums;
}
Expand All @@ -75,6 +78,9 @@ public Config(
if (structure != null) {
this.structure = structure;
}
if (generatedAnnotations != null) {
this.generatedAnnotations = generatedAnnotations;
}
}

public boolean isUppercaseEnums() {
Expand Down Expand Up @@ -104,4 +110,10 @@ public boolean isObjectExtraAnnotations() {
public CodeStructure getCodeStructure() {
return (structure == null) ? DEFAULT_CODE_STRUCTURE : structure;
}

public boolean isGeneratedAnnotations() {
return (generatedAnnotations == null)
? DEFAULT_ADD_GENERATED_ANNOTATIONS
: generatedAnnotations;
}
}
Expand Up @@ -16,6 +16,10 @@
package io.fabric8.java.generator.nodes;

import com.fasterxml.jackson.databind.JsonNode;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.Name;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import io.fabric8.java.generator.Config;
import io.fabric8.java.generator.exceptions.JavaGeneratorException;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
Expand All @@ -39,6 +43,10 @@ public abstract class AbstractJSONSchema2Pojo {
static final String OBJECT_CRD_TYPE = "object";
static final String ARRAY_CRD_TYPE = "array";

public static final AnnotationExpr GENERATED_ANNOTATION = new SingleMemberAnnotationExpr(
new Name("javax.annotation.processing.Generated"),
new StringLiteralExpr("io.fabric8.java-generator"));

protected final String description;
protected final Config config;
protected final boolean isNullable;
Expand Down
Expand Up @@ -113,6 +113,9 @@ public GeneratorResult generateJava() {
clz.addExtendedType(crType);
clz.addImplementedType("io.fabric8.kubernetes.api.model.Namespaced");

if (config.isGeneratedAnnotations()) {
clz.addAnnotation(GENERATED_ANNOTATION);
}
if (config.isObjectExtraAnnotations()) {
addExtraAnnotations(clz);
}
Expand Down
Expand Up @@ -158,6 +158,9 @@ public GeneratorResult generateJava() {
new NameExpr(
"using = com.fasterxml.jackson.databind.JsonDeserializer.None.class")));

if (config.isGeneratedAnnotations()) {
clz.addAnnotation(GENERATED_ANNOTATION);
}
if (config.isObjectExtraAnnotations()) {
addExtraAnnotations(clz);
}
Expand Down
Expand Up @@ -37,7 +37,7 @@ private static Stream<Arguments> getCRDGenerationInputData() {
return Stream.of(
Arguments.of("testCrontabCrd", "crontab-crd.yml", "CronTab", "CrontabJavaCr", new Config()),
Arguments.of("testCrontabExtraAnnotationsCrd", "crontab-crd.yml", "CronTab", "CrontabJavaExtraAnnotationsCr",
new Config(null, null, null, null, Boolean.TRUE, null)),
new Config(null, null, null, null, Boolean.TRUE, null, true)),
Arguments.of("testKeycloakCrd", "keycloak-crd.yml", "Keycloak", "KeycloakJavaCr", new Config()),
Arguments.of("testJokeCrd", "jokerequests-crd.yml", "JokeRequest", "JokeRequestJavaCr", new Config()),
Arguments.of("testAkkaMicroservicesCrd", "akka-microservices-crd.yml", "AkkaMicroservice", "AkkaMicroserviceJavaCr",
Expand Down
Expand Up @@ -41,7 +41,8 @@ class CompilationTest {

private static TemporaryFolder tmpFolder = TemporaryFolder.builder().build();

CRGeneratorRunner defaultRunner = new CRGeneratorRunner(new Config());
CRGeneratorRunner defaultRunner = new CRGeneratorRunner(
new Config(null, null, null, null, null, Config.CodeStructure.PACKAGE_NESTED, false));

List<JavaFileObject> getSources(File basePath) throws IOException {
List<JavaFileObject> sources = new ArrayList<JavaFileObject>();
Expand Down Expand Up @@ -87,7 +88,7 @@ void testCrontabCRDCompilesWithFlatPackage() throws Exception {
File crd = getCRD("crontab-crd.yml");
File dest = tmpFolder.newFolder("crontab-flat");
CRGeneratorRunner runner = new CRGeneratorRunner(
new Config(null, null, null, null, null, Config.CodeStructure.FLAT));
new Config(null, null, null, null, null, Config.CodeStructure.FLAT, false));

// Act
runner.run(crd, dest);
Expand All @@ -104,7 +105,7 @@ void testCrontabCRDCompilesWithExtraAnnotationsAndUnknownFields() throws Excepti
// Arrange
File crd = getCRD("crontab-crd.yml");
File dest = tmpFolder.newFolder("crontab-extra-annot");
CRGeneratorRunner runner = new CRGeneratorRunner(new Config(null, null, null, true, true, null));
CRGeneratorRunner runner = new CRGeneratorRunner(new Config(null, null, null, true, true, null, false));

// Act
runner.run(crd, dest);
Expand Down
Expand Up @@ -256,7 +256,7 @@ void testEmptyObject() {
@Test
void testEmptyObjectWithSuffix() {
// Arrange
Config config = new Config(null, null, Config.Suffix.ALWAYS, null, null, null);
Config config = new Config(null, null, Config.Suffix.ALWAYS, null, null, null, true);
JObject obj = new JObject(
"v1alpha1",
"t",
Expand Down Expand Up @@ -368,6 +368,47 @@ void testObjectWithRequiredField() {
assertTrue(clz.get().getFieldByName("o1").get().getAnnotationByName("Required").isPresent());
}

@Test
void testObjectWithAndWithoutGeneratedAnnotation() {
// Arrange
JObject obj1 = new JObject(
"v1alpha1",
"t",
new HashMap<>(),
new ArrayList<>(),
false,
"",
"",
defaultConfig,
null,
Boolean.FALSE,
null);
Config config = new Config(null, null, Config.Suffix.ALWAYS, null, null, null, false);
JObject obj2 = new JObject(
"v1alpha1",
"t",
new HashMap<>(),
new ArrayList<>(),
false,
"",
"",
config,
null,
Boolean.FALSE,
null);

// Act
GeneratorResult res1 = obj1.generateJava();
GeneratorResult res2 = obj2.generateJava();

// Assert
Optional<ClassOrInterfaceDeclaration> clz1 = res1.getTopLevelClasses().get(0).getCompilationUnit().getClassByName("T");
assertTrue(clz1.get().getAnnotationByName(AbstractJSONSchema2Pojo.GENERATED_ANNOTATION.getNameAsString()).isPresent());

Optional<ClassOrInterfaceDeclaration> clz2 = res2.getTopLevelClasses().get(0).getCompilationUnit().getClassByName("T");
assertFalse(clz2.get().getAnnotationByName(AbstractJSONSchema2Pojo.GENERATED_ANNOTATION.getNameAsString()).isPresent());
}

@Test
void testDefaultEnum() {
// Arrange
Expand Down Expand Up @@ -418,7 +459,7 @@ void testNotUppercaseEnum() {
JEnum enu = new JEnum(
"t",
enumValues,
new Config(false, null, null, null, null, null),
new Config(false, null, null, null, null, null, true),
null,
Boolean.FALSE,
null);
Expand Down Expand Up @@ -538,7 +579,7 @@ void testObjectOfObjects() {
@Test
void testObjectOfObjectsWithTopLevelPrefix() {
// Arrange
Config config = new Config(null, Config.Prefix.TOP_LEVEL, null, null, null, null);
Config config = new Config(null, Config.Prefix.TOP_LEVEL, null, null, null, null, true);
Map<String, JSONSchemaProps> props = new HashMap<>();
JSONSchemaProps newObj = new JSONSchemaProps();
newObj.setType("object");
Expand Down Expand Up @@ -568,7 +609,7 @@ void testObjectOfObjectsWithTopLevelPrefix() {
@Test
void testObjectOfObjectsWithAlwaysPrefix() {
// Arrange
Config config = new Config(null, Config.Prefix.ALWAYS, null, null, null, null);
Config config = new Config(null, Config.Prefix.ALWAYS, null, null, null, null, true);
Map<String, JSONSchemaProps> props = new HashMap<>();
JSONSchemaProps newObj = new JSONSchemaProps();
newObj.setType("object");
Expand Down

0 comments on commit b05049b

Please sign in to comment.