Skip to content

Commit

Permalink
Merge pull request #82 from realCity/feature/fluent-builder
Browse files Browse the repository at this point in the history
fluent-builder: improve inheritance handling
  • Loading branch information
mklemm authored Sep 13, 2024
2 parents ad6dab1 + c5159fa commit 0cd5a49
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class BuilderGenerator {
this.builderClass = new GenerifiedClass(builderOutline.getDefinedBuilderClass(), BuilderGenerator.PARENT_BUILDER_TYPE_PARAMETER_NAME);
this.resources = ResourceBundle.getBundle(BuilderGenerator.class.getName());
this.implement = !this.builderClass.raw.isInterface();
if (builderOutline.getClassOutline().getSuperClass() == null || !builderOutline.getClassOutline().getSuperClass().isLocal()) {
if (!isSuperClassBuildable(builderOutline.getClassOutline())) {
final JMethod endMethod = this.builderClass.raw.method(JMod.PUBLIC, this.builderClass.typeParam, this.settings.getEndMethodName());
if (this.implement) {
this.parentBuilderField = this.builderClass.raw.field(JMod.PROTECTED | JMod.FINAL, this.builderClass.typeParam, BuilderGenerator.PARENT_BUILDER_PARAM_NAME);
Expand Down Expand Up @@ -212,7 +212,7 @@ private void generateAddMethods(final PropertyOutline propertyOutline,
if (childBuilderOutline != null && !childBuilderOutline.getClassOutline().getImplClass().isAbstract()) {
final JClass builderWithMethodReturnType = childBuilderOutline.getBuilderClass().narrow(this.builderClass.type.wildcard());
addMethod = this.builderClass.raw.method(JMod.PUBLIC, builderWithMethodReturnType, PluginContext.ADD_METHOD_PREFIX + propertyName);
generateBuilderMethodJavadoc(addMethod, "add", fieldName, schemaAnnotation);
generateBuilderMethodJavadoc(addMethod, ADD_METHOD_PREFIX, fieldName, schemaAnnotation);
} else {
addMethod = null;
}
Expand Down Expand Up @@ -478,10 +478,10 @@ void generateBuilderMemberOverride(final PropertyOutline superPropertyOutline, f
final BuilderOutline childBuilderOutline = getBuilderDeclaration(fieldType);
if (childBuilderOutline != null && !childBuilderOutline.getClassOutline().getImplClass().isAbstract()) {
final JClass builderFieldElementType = childBuilderOutline.getBuilderClass().narrow(this.builderClass.type.wildcard());
final JMethod addMethod = this.builderClass.raw.method(JMod.PUBLIC, builderFieldElementType, WITH_METHOD_PREFIX + superPropertyName);
generateBuilderMethodJavadoc(addMethod, WITH_METHOD_PREFIX, superPropertyOutline.getFieldName(), propertyOutline.getSchemaAnnotationText().orElse(null));
final JMethod withChildBuilderMethod = this.builderClass.raw.method(JMod.PUBLIC, builderFieldElementType, WITH_METHOD_PREFIX + superPropertyName);
generateBuilderMethodJavadoc(withChildBuilderMethod, WITH_METHOD_PREFIX, superPropertyOutline.getFieldName(), propertyOutline.getSchemaAnnotationText().orElse(null));
if (this.implement) {
addMethod.body()._return(JExpr.cast(builderFieldElementType, JExpr._super().invoke(addMethod)));
withChildBuilderMethod.body()._return(JExpr.cast(builderFieldElementType, JExpr._super().invoke(withChildBuilderMethod)));
}
}
}
Expand Down Expand Up @@ -535,7 +535,7 @@ JMethod generateNewBuilderMethod() {
}

JMethod generateCopyOfMethod(final TypeOutline paramType, final boolean partial) {
if (paramType.getSuperClass() != null && paramType.getSuperClass().isLocal()) {
if (isSuperClassBuildable(paramType)) {
generateCopyOfMethod(paramType.getSuperClass(), partial);
}
final JMethod copyOfMethod = this.definedClass.method(JMod.PUBLIC | JMod.STATIC, this.builderClass.raw.narrow(Void.class), this.pluginContext.buildCopyMethodName);
Expand Down Expand Up @@ -563,15 +563,15 @@ JMethod generateNewCopyBuilderMethod(final boolean partial) {
copyBuilderMethod.body()._return(copyGenerator.generatePartialArgs(this.pluginContext._new((JClass)copyBuilderMethod.type()).arg(parentBuilderParam).arg(JExpr._this()).arg(JExpr.TRUE)));
copyBuilderConvenienceMethod.body()._return(copyConvenienceGenerator.generatePartialArgs(this.pluginContext.invoke(this.settings.getNewCopyBuilderMethodName()).arg(JExpr._null())));
}
if (this.typeOutline.getSuperClass() != null && this.typeOutline.getSuperClass().isLocal()) {
if (isSuperClassBuildable(this.typeOutline)) {
copyBuilderMethod.annotate(Override.class);
copyBuilderConvenienceMethod.annotate(Override.class);
}
return copyBuilderMethod;
}

private JMethod generateConveniencePartialCopyMethod(final TypeOutline paramType, final JMethod partialCopyOfMethod, final String methodName, final JExpression propertyTreeUseArg) {
if (paramType.getSuperClass() != null && paramType.getSuperClass().isLocal()) {
if (isSuperClassBuildable(paramType)) {
generateConveniencePartialCopyMethod(paramType.getSuperClass(), partialCopyOfMethod, methodName, propertyTreeUseArg);
}
final JMethod conveniencePartialCopyMethod = this.definedClass.method(JMod.PUBLIC | JMod.STATIC, this.builderClass.raw.narrow(Void.class), methodName);
Expand Down Expand Up @@ -604,7 +604,7 @@ final void generateCopyToMethod(final boolean partial) {
final CopyGenerator cloneGenerator = this.pluginContext.createCopyGenerator(copyToMethod, partial);
final JBlock body = copyToMethod.body();
final JVar otherRef;
if (this.typeOutline.getSuperClass() != null && this.typeOutline.getSuperClass().isLocal()) {
if (isSuperClassBuildable(this.typeOutline)) {
body.add(cloneGenerator.generatePartialArgs(this.pluginContext.invoke(JExpr._super(), copyToMethod.name()).arg(otherParam)));
}
otherRef = otherParam;
Expand All @@ -620,14 +620,14 @@ final void generateCopyConstructor(final boolean partial) {
final JVar otherParam = constructor.param(JMod.FINAL, this.typeOutline.getImplClass(), BuilderGenerator.OTHER_PARAM_NAME);
final JVar copyParam = constructor.param(JMod.FINAL, this.pluginContext.codeModel.BOOLEAN, BuilderGenerator.COPY_FLAG_PARAM_NAME);
final CopyGenerator cloneGenerator = this.pluginContext.createCopyGenerator(constructor, partial);
if (this.typeOutline.getSuperClass() != null && this.typeOutline.getSuperClass().isLocal()) {
if (isSuperClassBuildable(this.typeOutline)) {
constructor.body().add(cloneGenerator.generatePartialArgs(this.pluginContext._super().arg(parentBuilderParam).arg(otherParam).arg(copyParam)));
} else {
constructor.body().assign(JExpr._this().ref(this.parentBuilderField), parentBuilderParam);
}
final JConditional ifNullStmt = constructor.body()._if(otherParam.ne(JExpr._null()));
final JBlock body;
if (!this.settings.isCopyAlways() && (this.typeOutline.getSuperClass() == null || !this.typeOutline.getSuperClass().isLocal())) {
if (!this.settings.isCopyAlways() && !isSuperClassBuildable(this.typeOutline)) {
final JConditional ifCopyStmt = ifNullStmt._then()._if(copyParam);
ifCopyStmt._else().assign(this.storedValueField, otherParam);
ifNullStmt._else().assign(this.storedValueField, JExpr._null());
Expand Down Expand Up @@ -702,7 +702,6 @@ private void generateFieldCopyExpressions(final CopyGenerator cloneGenerator, fi
}

public void buildProperties() throws SAXException {
final TypeOutline superClass = this.typeOutline.getSuperClass();
final JMethod initMethod;
final JVar productParam;
final JBlock initBody;
Expand All @@ -726,9 +725,11 @@ public void buildProperties() throws SAXException {
}
}
}
if (superClass != null && superClass.isLocal()) {
if (isSuperClassBuildable(this.typeOutline)) {
final TypeOutline superClass = this.typeOutline.getSuperClass();
BuilderOutline superClassBuilder = getBuilderDeclaration(superClass.getImplClass());
if (superClassBuilder == null) throw new RuntimeException("Cannot find builder class name " + this.settings.getBuilderClassName().getClassName() + " of: " + superClass.getImplClass());
if (superClassBuilder == null)
throw new RuntimeException("Cannot find builder class name " + this.settings.getBuilderClassName().getClassName() + " of: " + superClass.getImplClass());
generateExtendsClause(superClassBuilder);
if (this.implement) initBody._return(JExpr._super().invoke(initMethod).arg(productParam));
generateBuilderMemberOverrides(superClass);
Expand Down Expand Up @@ -806,7 +807,7 @@ BuilderOutline getReferencedBuilderDeclaration(final PropertyOutline propertyOut
if(referencedDefinedClass != null) {
return this.builderOutlines.get(referencedDefinedClass.fullName());
} else {
return getReferencedBuilderOutline(propertyOutline.getRawType());
return getBuilderDeclaration(propertyOutline.getRawType());
}
}

Expand Down Expand Up @@ -888,7 +889,9 @@ private BuilderOutline getReferencedBuilderOutline(final JType type) {
if (this.pluginContext.getClassOutline(type) == null && this.pluginContext.getEnumOutline(type) == null && type.isReference() && !type.isArray() && type.fullName().contains(".")) {
final Class<?> runtimeParentClass;
try {
runtimeParentClass = Class.forName(type.binaryName());
// The Context ClassLoader is used to allow for tests to include files from previous runs on the classpath.
// PluginRunTest#testGenerateNetex contains an example.
runtimeParentClass = Class.forName(type.binaryName(), true, Thread.currentThread().getContextClassLoader());
} catch (final ClassNotFoundException e) {
return null;
}
Expand Down Expand Up @@ -917,4 +920,11 @@ private String getMessage(final String resourceKey, final Object... args) {
return MessageFormat.format(this.resources.getString(resourceKey), args);
}

private boolean isSuperClassBuildable(TypeOutline classOutline) {
var superClass = classOutline.getSuperClass();
if (superClass == null) {
return false;
}
return superClass.isLocal() || getReferencedBuilderOutline(superClass.getImplClass()) != null;
}
}
40 changes: 40 additions & 0 deletions plugin/src/test/java/com/kscs/util/test/PluginRunTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
package com.kscs.util.test;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -134,6 +136,44 @@ public void generateAndCompile(final String subDir, final String... pluginArgs)
compileTestCode(subDir);
}

@Test
public void testGenerateSiri() throws Exception {
generateAndCompile("siri",
inFile("siri.xsd"),
"-Xfluent-builder"
);
}

@Test
public void testGenerateNetex() throws Exception {
final var subDir = "netex";
final var episodeFile = generatedSourcesDir.resolve(subDir).resolve("siri.episode");
final var compiledCodeSubDir = compiledCodeDir.resolve(subDir);

generateAndCompile(subDir,
"-episode", episodeFile.toString(),
inFile("siri.xsd"),
"-Xfluent-builder"
);

ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader testClassLoader =
URLClassLoader.newInstance(new URL[]{compiledCodeSubDir.toUri().toURL()}, previousClassLoader);

try {
Thread.currentThread().setContextClassLoader(testClassLoader);

runPlugin(subDir,
"-b", episodeFile.toString(),
inFile("netex.xsd"),
"-Xfluent-builder"
);
compileTestCode(subDir);
} finally {
Thread.currentThread().setContextClassLoader(previousClassLoader);
}
}

@Test
public void testGenerateAll() throws Exception {
generateAndCompile("all","-b", inFile("binding-config.xjb"),
Expand Down
24 changes: 24 additions & 0 deletions plugin/src/test/resources/netex.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.netex.org.uk/netex"
xmlns:siri="http://www.siri.org.uk/siri"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.netex.org.uk/netex"
elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.siri.org.uk/siri siri.xsd">

<xs:import namespace="http://www.siri.org.uk/siri"
schemaLocation="siri.xsd"/>

<xsd:element name="DataObjectSubscriptionRequest" type="DataObjectSubscriptionStructure" />

<xsd:complexType name="DataObjectSubscriptionStructure">
<xsd:complexContent>
<xsd:extension base="siri:AbstractSubscriptionStructure">
<xsd:sequence>
<xsd:element name="DataObjectRequest"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>
53 changes: 53 additions & 0 deletions plugin/src/test/resources/siri.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.siri.org.uk/siri" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.siri.org.uk/siri" elementFormDefault="qualified"
attributeFormDefault="unqualified" version="2.1" id="siri_vehicleMonitoring_service">

<xsd:complexType name="AbstractSubscriptionStructure" abstract="true">
<xsd:sequence>
<xsd:element name="InitialTerminationTime" type="xsd:dateTime">
<xsd:annotation>
<xsd:documentation>Requested end time for subscription.</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="VehicleActivityStructure">
<xsd:sequence>
<xsd:element name="MonitoredVehicleJourney">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="MonitoredVehicleJourneyStructure"/>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="MonitoredVehicleJourneyStructure">
<xsd:sequence>
<xsd:element name="TrainNumbers" minOccurs="0">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="TrainNumberRef" type="TrainNumberRefStructure" maxOccurs="unbounded">
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TrainNumberRefStructure">
<xsd:annotation>
<xsd:documentation>Type for reference to a TRAIN NUMBER</xsd:documentation>
</xsd:annotation>
<xsd:simpleContent>
<xsd:extension base="TrainNumber"/>
</xsd:simpleContent>
</xsd:complexType>
<xsd:simpleType name="TrainNumber">
<xsd:annotation>
<xsd:documentation>Type for identifier of an TRAIN NUMBER</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN"/>
</xsd:simpleType>
</xsd:schema>

0 comments on commit 0cd5a49

Please sign in to comment.