Skip to content

Commit

Permalink
Support for javaType and existingJavaType extensions in java-generator
Browse files Browse the repository at this point in the history
  • Loading branch information
matteriben committed Mar 30, 2024
1 parent 0c80a66 commit ebdad5f
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#### Bugs

#### Improvements
* Fix #5843: Support javaType and existingJavaType extensions in java-generator

#### Dependency Upgrade

Expand Down
33 changes: 33 additions & 0 deletions doc/java-generation-from-CRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,39 @@ And the corresponding configurations of the Maven plugin are (output of `mvn hel
The URLs to be used to download CRDs from remote locations
```

## Extension support

The `JavaType` extension allows the fully qualified name of the generated class to be specified,
and potentially reused such as `Point` in the example snippet below. The `existingJavaType` extension
allows the generator to make use of an existing java type, such as `Affinity` in the example snippet
below.

```
spec:
versions:
- schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
affinity:
type: object
existingJavaType: io.fabric8.kubernetes.api.model.Affinity
point-A:
type: object
javaType: com.example.Point
properties:
x-position:
type: integer
y-position:
type: integer
point-B:
type: object
existingJavaType: com.example.Point
```

## Compiling the generated code

The generated code depends on a few dependencies to successfully compile:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,20 @@ private static AbstractJSONSchema2Pojo fromJsonSchema(
prop.getDefault());
case OBJECT:
final boolean preserveUnknownFields = Boolean.TRUE.equals(prop.getXKubernetesPreserveUnknownFields());
// Added to support javaType and existingJavaType extensions for generating Java classes
boolean existingClass = false;
String type = null;
if (null != prop.getJavaType()) {
type = prop.getJavaType();
} else if (null != prop.getExistingJavaType()) {
type = prop.getExistingJavaType();
existingClass = true;
}
if (null != type) {
int index = type.lastIndexOf(".");
parentPkg = type.substring(0, Math.max(0, index));
key = type.substring(index + 1);
}
return new JObject(
parentPkg,
key,
Expand All @@ -303,7 +317,8 @@ private static AbstractJSONSchema2Pojo fromJsonSchema(
config,
prop.getDescription(),
isNullable,
prop.getDefault());
prop.getDefault(),
existingClass);
case ENUM:
String enumType = JAVA_LANG_STRING;
switch (prop.getType()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class JObject extends AbstractJSONSchema2Pojo implements JObjectExtraAnno
private final Set<String> deprecated = new HashSet<>();

private final boolean preserveUnknownFields;
private final boolean existingClass; // Added to support existingJavaType extensions for generating Java classes

public JObject(
String pkg,
Expand All @@ -63,11 +64,13 @@ public JObject(
Config config,
String description,
final boolean isNullable,
JsonNode defaultValue) {
JsonNode defaultValue,
boolean existingClass) {
super(config, description, isNullable, defaultValue, null);
this.required = new HashSet<>(Optional.ofNullable(required).orElse(Collections.emptyList()));
this.fields = new HashMap<>();
this.preserveUnknownFields = preserveUnknownFields;
this.existingClass = existingClass;

this.pkg = (pkg == null) ? "" : pkg.trim();
String pkgPrefix = (this.pkg.isEmpty()) ? this.pkg : this.pkg + ".";
Expand Down Expand Up @@ -153,6 +156,9 @@ private String getSortedFieldsAsParam(Set<String> list) {

@Override
public GeneratorResult generateJava() {
if (existingClass) {
return new GeneratorResult(Collections.emptyList());
}
CompilationUnit cu = new CompilationUnit();
if (!this.pkg.isEmpty()) {
cu.setPackageDeclaration(new PackageDeclaration(new Name(this.pkg)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ private static Stream<Arguments> getCRDGenerationInputData() {
Arguments.of("testJokeCrd", "jokerequests-crd.yml", "JokeRequest", "JokeRequestJavaCr", new Config()),
Arguments.of("testAkkaMicroservicesCrd", "akka-microservices-crd.yml", "AkkaMicroservice", "AkkaMicroserviceJavaCr",
new Config()),
Arguments.of("testCalicoIPPoolCrd", "calico-ippool-crd.yml", "IPPool", "CalicoIPPoolCr", new Config()));
Arguments.of("testCalicoIPPoolCrd", "calico-ippool-crd.yml", "IPPool", "CalicoIPPoolCr", new Config()),
Arguments.of("testJavaType", "java-type-crd.yml", "JavaType", "JavaTypeCr", new Config()));
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ void testEmptyObject() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand All @@ -318,7 +319,8 @@ void testEmptyObjectWithoutNamespace() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand All @@ -345,7 +347,8 @@ void testObjectOfPrimitives() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand Down Expand Up @@ -379,7 +382,8 @@ void testObjectWithRequiredField() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand All @@ -401,7 +405,8 @@ void testObjectWithAndWithoutGeneratedAnnotation() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);
Config config = new Config(null, null, false, new HashMap<>());
JObject obj2 = new JObject(
"v1alpha1",
Expand All @@ -412,7 +417,8 @@ void testObjectWithAndWithoutGeneratedAnnotation() {
config,
null,
Boolean.FALSE,
null);
null,
false);
String generatedAnnotationName = AbstractJSONSchema2Pojo.newGeneratedAnnotation().getNameAsString();

// Act
Expand Down Expand Up @@ -592,7 +598,8 @@ void testArrayOfObjects() {
defaultConfig,
null,
Boolean.FALSE,
null),
null,
false),
defaultConfig,
null,
false,
Expand Down Expand Up @@ -620,7 +627,8 @@ void testMapOfObjects() {
defaultConfig,
null,
Boolean.FALSE,
null),
null,
false),
defaultConfig,
null,
Boolean.FALSE,
Expand Down Expand Up @@ -651,7 +659,8 @@ void testObjectOfObjects() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand Down Expand Up @@ -681,7 +690,8 @@ void testObjectWithPreservedFields() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand All @@ -708,7 +718,8 @@ void testConfigToGeneratePreservedUnknownFields() {
config,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand Down Expand Up @@ -739,7 +750,8 @@ void testObjectWithSpecialFieldNames() {
defaultConfig,
null,
Boolean.FALSE,
null);
null,
false);

// Act
GeneratorResult res = obj.generateJava();
Expand Down Expand Up @@ -767,7 +779,7 @@ void testObjectNullableFieldsManagement() {
nonNullableObj.setNullable(Boolean.FALSE);
props.put("o2", nonNullableObj);

JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null);
JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null, false);

// Act
GeneratorResult res = obj.generateJava();
Expand Down Expand Up @@ -816,7 +828,7 @@ void testObjectWithAdditionalPropertiesTrue() {
obj.setAdditionalProperties(new JSONSchemaPropsOrBool(true, null));
props.put("o1", obj);

JObject jobj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null);
JObject jobj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null, false);

// Act
GeneratorResult res = jobj.generateJava();
Expand Down Expand Up @@ -850,7 +862,7 @@ void testClassNamesDisambiguationWithPackageNesting() {
newObj2.setProperties(obj2Props);
props.put("o1", newObj1);
props.put("o2", newObj2);
JObject obj = new JObject("v1alpha1", "t", props, null, false, defaultConfig, null, Boolean.FALSE, null);
JObject obj = new JObject("v1alpha1", "t", props, null, false, defaultConfig, null, Boolean.FALSE, null, false);

// Act
GeneratorResult res = obj.generateJava();
Expand Down Expand Up @@ -905,7 +917,7 @@ void testObjectDefaultFieldsManagement() {
objWithoutDefaultValues.setType("object");
props.put("o2", objWithoutDefaultValues);

JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null);
JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null, false);

// Act
GeneratorResult res = obj.generateJava();
Expand Down Expand Up @@ -959,7 +971,7 @@ void testExactlyMoreThanOneDuplicatedFieldFailsWithException() {
// Assert
assertThrows(JavaGeneratorException.class, () -> {
// Act
JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null);
JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null, false);
},
"An exception is expected to be thrown when an object contains more that one duplicated field");
}
Expand All @@ -978,8 +990,31 @@ void testExactlyDuplicatedFieldNotMarkedAsDeprecatedFailsWithException() {
// Assert
assertThrows(JavaGeneratorException.class, () -> {
// Act
JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null);
JObject obj = new JObject(null, "t", props, null, false, defaultConfig, null, Boolean.FALSE, null, false);
},
"An exception is expected to be thrown when an object contains one duplicated field that is not marked as deprecated");
}

@Test
void testExistingJavaTypeObject() {
// Arrange
JObject obj = new JObject(
"v1alpha1",
"T",
null,
null,
false,
defaultConfig,
null,
Boolean.FALSE,
null,
true);

// Act
GeneratorResult res = obj.generateJava();

// Assert
assertEquals("v1alpha1.T", obj.getType());
assertEquals(0, res.getTopLevelClasses().size());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
JavaTypeCr[0] = package org.test.v1;

@io.fabric8.kubernetes.model.annotation.Version(value = "v1" , storage = true , served = true)
@io.fabric8.kubernetes.model.annotation.Group("example.com")
@io.fabric8.kubernetes.model.annotation.Plural("javatypes")
@javax.annotation.processing.Generated("io.fabric8.java.generator.CRGeneratorRunner")
public class JavaType extends io.fabric8.kubernetes.client.CustomResource<org.test.v1.JavaTypeSpec, java.lang.Void> implements io.fabric8.kubernetes.api.model.Namespaced {
}

JavaTypeCr[1] = package org.test.v1;

@com.fasterxml.jackson.annotation.JsonInclude(com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL)
@com.fasterxml.jackson.annotation.JsonPropertyOrder({"affinity","point-A","point-B"})
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
@javax.annotation.processing.Generated("io.fabric8.java.generator.CRGeneratorRunner")
public class JavaTypeSpec implements io.fabric8.kubernetes.api.model.KubernetesResource {

@com.fasterxml.jackson.annotation.JsonProperty("affinity")
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SKIP)
private io.fabric8.kubernetes.api.model.Affinity affinity;

public io.fabric8.kubernetes.api.model.Affinity getAffinity() {
return affinity;
}

public void setAffinity(io.fabric8.kubernetes.api.model.Affinity affinity) {
this.affinity = affinity;
}

@com.fasterxml.jackson.annotation.JsonProperty("point-A")
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SKIP)
private com.example.Point pointA;

public com.example.Point getPointA() {
return pointA;
}

public void setPointA(com.example.Point pointA) {
this.pointA = pointA;
}

@com.fasterxml.jackson.annotation.JsonProperty("point-B")
@com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SKIP)
private com.example.Point pointB;

public com.example.Point getPointB() {
return pointB;
}

public void setPointB(com.example.Point pointB) {
this.pointB = pointB;
}
}

Loading

0 comments on commit ebdad5f

Please sign in to comment.