org.babyfish.jimmer.apt.dto.DtoGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jimmer-apt Show documentation
Show all versions of jimmer-apt Show documentation
A revolutionary ORM framework for both java and kotlin
package org.babyfish.jimmer.apt.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.squareup.javapoet.*;
import org.babyfish.jimmer.apt.Context;
import org.babyfish.jimmer.apt.GeneratorException;
import org.babyfish.jimmer.apt.immutable.meta.ImmutableProp;
import org.babyfish.jimmer.apt.immutable.meta.ImmutableType;
import org.babyfish.jimmer.apt.util.ConverterMetadata;
import org.babyfish.jimmer.client.ApiIgnore;
import org.babyfish.jimmer.client.meta.Doc;
import org.babyfish.jimmer.dto.compiler.*;
import org.babyfish.jimmer.impl.util.StringUtil;
import org.babyfish.jimmer.runtime.ImmutableSpi;
import org.babyfish.jimmer.sql.Id;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.processing.Filer;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.*;
import static org.babyfish.jimmer.apt.util.GeneratedAnnotation.generatedAnnotation;
public class DtoGenerator {
private static final String[] EMPTY_STR_ARR = new String[0];
private final Context ctx;
final DtoType dtoType;
private final Document document;
private final Filer filer;
private final DtoGenerator parent;
private final DtoGenerator root;
private final String innerClassName;
private final Set interfaceMethodNames;
private TypeSpec.Builder typeBuilder;
public DtoGenerator(
Context ctx,
DtoType dtoType,
Filer filer
) {
this(ctx, dtoType, filer, null, null);
}
private DtoGenerator(
Context ctx,
DtoType dtoType,
Filer filer,
DtoGenerator parent,
String innerClassName
) {
if ((filer == null) == (parent == null)) {
throw new IllegalArgumentException("The nullity values of `filer` and `parent` cannot be same");
}
if ((parent == null) != (innerClassName == null)) {
throw new IllegalArgumentException("The nullity values of `parent` and `innerClassName` must be same");
}
this.ctx = ctx;
this.dtoType = dtoType;
this.document = new Document(ctx, dtoType);
this.filer = filer;
this.parent = parent;
this.root = parent != null ? parent.root : this;
this.innerClassName = innerClassName;
this.interfaceMethodNames = DtoInterfaces.abstractMethodNames(ctx, dtoType);
}
public void generate() {
String simpleName = getSimpleName();
typeBuilder = TypeSpec
.classBuilder(simpleName)
.addModifiers(Modifier.PUBLIC);
if (isImpl() && dtoType.getBaseType().isEntity()) {
typeBuilder.addSuperinterface(
dtoType.getModifiers().contains(DtoModifier.SPECIFICATION) ?
ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.JSPECIFICATION_CLASS_NAME,
dtoType.getBaseType().getClassName(),
dtoType.getBaseType().getTableClassName()
) :
ParameterizedTypeName.get(
dtoType.getModifiers().contains(DtoModifier.INPUT) ?
org.babyfish.jimmer.apt.immutable.generator.Constants.INPUT_CLASS_NAME :
org.babyfish.jimmer.apt.immutable.generator.Constants.VIEW_CLASS_NAME,
dtoType.getBaseType().getClassName()
)
);
}
if (isImpl() && dtoType.getBaseType().isEmbeddable()) {
typeBuilder.addSuperinterface(
ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.EMBEDDABLE_DTO_CLASS_NAME,
dtoType.getBaseType().getClassName()
)
);
}
for (TypeRef typeRef : dtoType.getSuperInterfaces()) {
typeBuilder.addSuperinterface(getTypeName(typeRef));
}
if (isHibernateValidatorEnhancementRequired()) {
typeBuilder.addSuperinterface(
org.babyfish.jimmer.apt.immutable.generator.Constants.HIBERNATE_VALIDATOR_ENHANCED_BEAN
);
}
if (parent == null) {
typeBuilder.addAnnotation(generatedAnnotation(dtoType.getDtoFile()));
} else {
typeBuilder.addAnnotation(generatedAnnotation());
}
if (isBuildRequired()) {
typeBuilder.addAnnotation(
AnnotationSpec
.builder(org.babyfish.jimmer.apt.immutable.generator.Constants.JSON_DESERIALIZE_CLASS_NAME)
.addMember(
"builder",
"$T.class",
getDtoClassName("Builder")
)
.build()
);
}
String doc = document.get();
if (doc == null) {
doc = ctx.getElements().getDocComment(dtoType.getBaseType().getTypeElement());
}
if (doc != null) {
typeBuilder.addJavadoc(doc.replace("$", "$$"));
}
for (AnnotationMirror annotationMirror : dtoType.getBaseType().getTypeElement().getAnnotationMirrors()) {
if (isCopyableAnnotation(annotationMirror, dtoType.getAnnotations(), null)) {
typeBuilder.addAnnotation(AnnotationSpec.get(annotationMirror));
}
}
for (Anno anno : dtoType.getAnnotations()) {
typeBuilder.addAnnotation(annotationOf(anno));
}
if (innerClassName != null) {
typeBuilder.addModifiers(Modifier.STATIC);
addMembers();
} else {
addMembers();
}
if (innerClassName != null) {
assert parent != null;
parent.typeBuilder.addType(typeBuilder.build());
} else {
try {
JavaFile
.builder(
root.dtoType.getPackageName(),
typeBuilder.build()
)
.indent(" ")
.build()
.writeTo(filer);
} catch (IOException ex) {
throw new GeneratorException(
String.format(
"Cannot generate dto type '%s' for '%s'",
dtoType.getName(),
dtoType.getBaseType().getQualifiedName()
),
ex
);
}
}
}
public String getSimpleName() {
return innerClassName != null ? innerClassName : dtoType.getName();
}
private ClassName getDtoClassName() {
return getDtoClassName(null);
}
ClassName getDtoClassName(String nestedClassName) {
if (innerClassName != null) {
List list = new ArrayList<>();
collectNames(list);
List simpleNames = list.subList(1, list.size());
if (nestedClassName != null) {
simpleNames = new ArrayList<>(simpleNames);
simpleNames.add(nestedClassName);
}
return ClassName.get(
root.dtoType.getPackageName(),
list.get(0),
simpleNames.toArray(EMPTY_STR_ARR)
);
}
if (nestedClassName == null) {
return ClassName.get(
root.dtoType.getPackageName(),
dtoType.getName()
);
}
return ClassName.get(
root.dtoType.getPackageName(),
dtoType.getName(),
nestedClassName
);
}
private void addMembers() {
boolean isSpecification = dtoType.getModifiers().contains(DtoModifier.SPECIFICATION);
if (!isSpecification) {
addMetadata();
}
if (!dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
for (DtoProp prop : dtoType.getDtoProps()) {
addAccessorField(prop);
}
}
for (DtoProp prop : dtoType.getDtoProps()) {
addField(prop);
addStateField(prop);
}
for (UserProp prop : dtoType.getUserProps()) {
addField(prop);
}
addDefaultConstructor();
if (!isSpecification) {
addConverterConstructor();
}
for (DtoProp prop : dtoType.getDtoProps()) {
addAccessors(prop);
}
for (UserProp prop : dtoType.getUserProps()) {
addAccessors(prop);
}
if (dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
addEntityType();
addApplyTo();
} else {
addToEntity();
}
addHashCode();
addEquals();
addToString();
for (DtoProp prop : dtoType.getDtoProps()) {
addSpecificationConverter(prop);
}
for (DtoProp prop : dtoType.getDtoProps()) {
DtoType targetType = prop.getTargetType();
if (targetType == null) {
continue;
}
if (!prop.isRecursive() || targetType.isFocusedRecursion()) {
new DtoGenerator(
ctx,
targetType,
null,
this,
targetSimpleName(prop)
).generate();
}
}
if (isBuildRequired()) {
new InputBuilderGenerator(this).generate();
}
if (isHibernateValidatorEnhancementRequired()) {
addHibernateValidatorEnhancement(false);
addHibernateValidatorEnhancement(true);
}
}
private void addMetadata() {
FieldSpec.Builder builder = FieldSpec
.builder(
ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_METADATA_CLASS_NAME,
dtoType.getBaseType().getClassName(),
getDtoClassName()
),
"METADATA"
)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL);
CodeBlock.Builder cb = CodeBlock
.builder()
.indent()
.add("\n")
.add(
"new $T<$T, $T>(\n",
org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_METADATA_CLASS_NAME,
dtoType.getBaseType().getClassName(),
getDtoClassName()
)
.indent()
.add("$T.$L", dtoType.getBaseType().getFetcherClassName(), "$")
.indent();
for (DtoProp prop : dtoType.getDtoProps()) {
if (prop.getNextProp() == null) {
addFetcherField(prop, cb);
}
}
for (DtoProp hiddenProp : dtoType.getHiddenFlatProps()) {
if (!hiddenProp.getBaseProp().isId()) {
addHiddenFetcherField(hiddenProp, cb);
}
}
cb
.add(",\n")
.unindent()
.add("$T::new\n", getDtoClassName())
.unindent()
.unindent()
.add(")");
builder.initializer(cb.build());
typeBuilder.addField(builder.build());
}
private void addFetcherField(DtoProp prop, CodeBlock.Builder cb) {
if (prop.getBaseProp().getAnnotation(Id.class) == null) {
if (prop.getTargetType() != null) {
if (prop.getTargetType() != null) {
if (prop.isRecursive()) {
cb.add("\n.$N()", StringUtil.identifier("recursive", prop.getBaseProp().getName()));
} else {
cb.add("\n.$N($T.METADATA.getFetcher())", prop.getBaseProp().getName(), getPropElementName(prop));
}
}
} else {
cb.add("\n.$N()", prop.getBaseProp().getName());
}
}
}
private void addAccessorField(DtoProp prop) {
if (isSimpleProp(prop)) {
return;
}
FieldSpec.Builder builder = FieldSpec.builder(
org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_PROP_ACCESSOR_CLASS_NAME,
StringUtil.snake(prop.getName() + "Accessor", StringUtil.SnakeCase.UPPER),
Modifier.PRIVATE,
Modifier.STATIC,
Modifier.FINAL
);
CodeBlock.Builder cb = CodeBlock.builder();
cb.add("new $T(", org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_PROP_ACCESSOR_CLASS_NAME);
cb.indent();
DtoProp tailProp = prop.toTailProp();
if (prop.isNullable() && (
!prop.toTailProp().getBaseProp().isNullable() ||
dtoType.getModifiers().contains(DtoModifier.SPECIFICATION) ||
dtoType.getModifiers().contains(DtoModifier.FUZZY) ||
prop.getInputModifier() == DtoModifier.FUZZY)
) {
cb.add("\nfalse");
} else {
cb.add("\ntrue");
}
if (prop.getNextProp() == null) {
cb.add(",\nnew int[] { $T.$L }", dtoType.getBaseType().getProducerClassName(), prop.getBaseProp().getSlotName());
} else {
cb.add(",\nnew int[] {");
cb.indent();
boolean addComma = false;
for (DtoProp p = prop; p != null; p = p.getNextProp()) {
if (addComma) {
cb.add(",");
} else {
addComma = true;
}
cb.add("\n$T.$L", p.getBaseProp().getDeclaringType().getProducerClassName(), p.getBaseProp().getSlotName());
}
cb.unindent();
cb.add("\n}");
}
if (prop.isIdOnly()) {
if (dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
cb.add(",\nnull");
} else {
cb.add(
",\n$T.$L($T.class, ",
org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_PROP_ACCESSOR_CLASS_NAME,
tailProp.getBaseProp().isList() ? "idListGetter" : "idReferenceGetter",
tailProp.getBaseProp().getTargetType().getClassName()
);
addConverterLoading(cb, prop, false);
cb.add(")");
cb.add(
",\n$T.$L($T.class, ",
org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_PROP_ACCESSOR_CLASS_NAME,
tailProp.getBaseProp().isList() ? "idListSetter" : "idReferenceSetter",
tailProp.getBaseProp().getTargetType().getClassName()
);
addConverterLoading(cb, prop, false);
cb.add(")");
}
} else if (tailProp.getTargetType() != null) {
if (dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
cb.add(",\nnull");
} else {
cb.add(
",\n$T.<$T, $T>$L($T::new)",
org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_PROP_ACCESSOR_CLASS_NAME,
tailProp.getBaseProp().getTargetType().getClassName(),
getPropElementName(tailProp),
tailProp.getBaseProp().isList() ? "objectListGetter" : "objectReferenceGetter",
getPropElementName(tailProp)
);
cb.add(
",\n$T.$L($T::$L)",
org.babyfish.jimmer.apt.immutable.generator.Constants.DTO_PROP_ACCESSOR_CLASS_NAME,
tailProp.getBaseProp().isList() ? "objectListSetter" : "objectReferenceSetter",
getPropElementName(tailProp),
tailProp.getTargetType().getBaseType().isEntity() ? "toEntity" : "toImmutable"
);
}
} else if (prop.getEnumType() != null) {
EnumType enumType = prop.getEnumType();
TypeName enumTypeName = tailProp.getBaseProp().getTypeName();
if (dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
cb.add(",\nnull");
} else {
cb.add(",\narg -> {\n");
cb.indent();
cb.beginControlFlow("switch (($T)arg)", enumTypeName);
for (Map.Entry e: enumType.getValueMap().entrySet()) {
cb.add("case $L:\n", e.getKey());
cb.indent();
cb.addStatement("return $L", e.getValue());
cb.unindent();
}
cb.add("default:\n");
cb.indent();
cb.addStatement(
"throw new AssertionError($S)",
"Internal bug"
);
cb.unindent();
cb.endControlFlow();
cb.unindent();
cb.add("}");
}
cb.add(",\narg -> {\n");
cb.indent();
addValueToEnum(cb, prop, "arg");
cb.unindent();
cb.add("}");
} else if (converterMetadataOf(prop) != null) {
cb.add(",\narg -> ");
addConverterLoading(cb, prop, true);
cb.add(".output(arg)");
cb.add(",\narg -> ");
addConverterLoading(cb, prop, true);
cb.add(".input(arg)");
}
cb.unindent();
cb.add("\n)");
builder.initializer(cb.build());
typeBuilder.addField(builder.build());
}
private void addValueToEnum(CodeBlock.Builder cb, DtoProp prop, String variableName) {
EnumType enumType = prop.getEnumType();
TypeName enumTypeName = prop.toTailProp().getBaseProp().getTypeName();
cb.beginControlFlow("switch (($T)$L)", enumType.isNumeric() ? TypeName.INT : org.babyfish.jimmer.apt.immutable.generator.Constants.STRING_CLASS_NAME, variableName);
for (Map.Entry e: enumType.getConstantMap().entrySet()) {
cb.add("case $L:\n", e.getKey());
cb.indent();
cb.addStatement("return $T.$L", enumTypeName, e.getValue());
cb.unindent();
}
cb.add("default:\n");
cb.indent();
cb.addStatement(
"throw new IllegalArgumentException($S + $L + $S)",
"Illegal value `\"",
variableName,
"\"`for enum type: \"" + enumTypeName + "\""
);
cb.unindent();
cb.endControlFlow();
}
private void addConverterLoading(CodeBlock.Builder cb, DtoProp prop, boolean forList) {
ImmutableProp baseProp = prop.toTailProp().getBaseProp();
cb.add(
"$T.$L.unwrap().$L",
dtoType.getBaseType().getPropsClassName(),
StringUtil.snake(baseProp.getName(), StringUtil.SnakeCase.UPPER),
prop.toTailProp().getBaseProp().isAssociation(true) ?
"getAssociatedIdConverter(" + forList + ")" :
"getConverter()"
);
}
private boolean isSimpleProp(DtoProp prop) {
if (prop.getNextProp() != null) {
return false;
}
if (prop.isNullable() && (
!prop.isBaseNullable() || dtoType.getModifiers().contains(DtoModifier.SPECIFICATION))) {
return false;
}
return getPropTypeName(prop).equals(prop.getBaseProp().getTypeName());
}
private void addHiddenFetcherField(DtoProp prop, CodeBlock.Builder cb) {
if (!"flat".equals(prop.getFuncName())) {
addFetcherField(prop, cb);
return;
}
DtoType targetDtoType = prop.getTargetType();
assert targetDtoType != null;
cb.add("\n.$N($>", prop.getBaseProp().getName());
cb.add("$T.$L$>", prop.getBaseProp().getTargetType().getFetcherClassName(), "$");
for (DtoProp childProp : targetDtoType.getDtoProps()) {
addHiddenFetcherField(childProp, cb);
}
cb.add("$<$<\n)");
}
private void addField(DtoProp prop) {
TypeName typeName = getPropTypeName(prop);
if (isFieldNullable(prop)) {
typeName = typeName.box();
}
FieldSpec.Builder builder = FieldSpec
.builder(typeName, prop.getName())
.addModifiers(Modifier.PRIVATE);
if (dtoType.getModifiers().contains(DtoModifier.INPUT) && prop.getInputModifier() == DtoModifier.FIXED) {
builder.addAnnotation(org.babyfish.jimmer.apt.immutable.generator.Constants.FIXED_INPUT_FIELD_CLASS_NAME);
}
for (AnnotationMirror annotationMirror : prop.getBaseProp().getAnnotations()) {
if (isCopyableAnnotation(annotationMirror, dtoType.getAnnotations(), false)) {
builder.addAnnotation(AnnotationSpec.get(annotationMirror));
}
}
for (Anno anno : prop.getAnnotations()) {
if (hasElementType(anno, ElementType.FIELD)) {
builder.addAnnotation(annotationOf(anno));
}
}
typeBuilder.addField(builder.build());
}
private void addField(UserProp prop) {
TypeName typeName = getPropTypeName(prop);
if (isFieldNullable(prop)) {
typeName = typeName.box();
}
FieldSpec.Builder builder = FieldSpec
.builder(typeName, prop.getAlias())
.addModifiers(Modifier.PRIVATE);
for (Anno anno : prop.getAnnotations()) {
if (hasElementType(anno, ElementType.FIELD)) {
builder.addAnnotation(annotationOf(anno));
}
}
typeBuilder.addField(builder.build());
}
private void addStateField(DtoProp prop) {
String stateFieldName = stateFieldName(prop, false);
if (stateFieldName == null) {
return;
}
typeBuilder.addField(
TypeName.BOOLEAN,
stateFieldName,
Modifier.PRIVATE
);
}
@SuppressWarnings("unchecked")
private void addAccessors(AbstractProp prop) {
TypeName typeName = getPropTypeName(prop);
String getterName = getterName(prop);
String setterName = setterName(prop);
String stateFieldName = stateFieldName(prop, false);
MethodSpec.Builder getterBuilder = MethodSpec
.methodBuilder(getterName)
.addModifiers(Modifier.PUBLIC)
.returns(typeName);
if (interfaceMethodNames.contains(getterName)) {
getterBuilder.addAnnotation(Override.class);
}
if (!(prop instanceof DtoProp, ?>) || ((DtoProp, ?>)prop).getNextProp() == null) {
String doc = document.get(prop);
if (prop instanceof DtoProp, ?>) {
DtoProp dtoProp = (DtoProp) prop;
if (doc == null && dtoProp.getBasePropMap().size() == 1 && dtoProp.getFuncName() == null) {
doc = ctx.getElements().getDocComment(dtoProp.getBaseProp().toElement());
}
}
if (doc != null) {
getterBuilder.addJavadoc(doc.replace("$", "$$"));
}
}
if (!typeName.isPrimitive()) {
if (prop.isNullable()) {
getterBuilder.addAnnotation(Nullable.class);
} else {
getterBuilder.addAnnotation(NotNull.class);
}
}
if (prop instanceof DtoProp, ?>) {
DtoProp dtoProp = (DtoProp) prop;
for (AnnotationMirror annotationMirror : dtoProp.getBaseProp().getAnnotations()) {
if (isCopyableAnnotation(annotationMirror, dtoProp.getAnnotations(), true)) {
getterBuilder.addAnnotation(AnnotationSpec.get(annotationMirror));
}
}
}
for (Anno anno : prop.getAnnotations()) {
if (hasElementType(anno, ElementType.METHOD)) {
getterBuilder.addAnnotation(annotationOf(anno));
}
}
if (stateFieldName != null) {
getterBuilder.beginControlFlow(
"if ($L)",
'!' + stateFieldName
);
getterBuilder.addStatement(
"throw new IllegalStateException($S)",
"The property \"" + prop.getName() + "\" is not specified"
);
getterBuilder.endControlFlow();
}
if (!prop.isNullable() && isFieldNullable(prop)) {
getterBuilder.beginControlFlow(
"if ($L == null)",
prop.getName()
);
getterBuilder.addStatement(
"throw new IllegalStateException($S)",
"The property \"" + prop.getName() + "\" is not specified"
);
getterBuilder.endControlFlow();
}
getterBuilder.addStatement("return $L", prop.getName());
typeBuilder.addMethod(getterBuilder.build());
ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(typeName, prop.getName());
if (!typeName.isPrimitive()) {
if (prop.isNullable()) {
parameterBuilder.addAnnotation(Nullable.class);
} else {
parameterBuilder.addAnnotation(NotNull.class);
}
}
MethodSpec.Builder setterBuilder = MethodSpec
.methodBuilder(setterName)
.addParameter(parameterBuilder.build())
.addModifiers(Modifier.PUBLIC);
if (interfaceMethodNames.contains(setterName)) {
setterBuilder.addAnnotation(Override.class);
}
setterBuilder.addStatement("this.$L = $L", prop.getName(), prop.getName());
if (stateFieldName != null) {
setterBuilder.addStatement("this.$L = true", stateFieldName);
}
typeBuilder.addMethod(setterBuilder.build());
if (stateFieldName != null) {
MethodSpec.Builder isLoadedBuilder = MethodSpec
.methodBuilder(StringUtil.identifier("is", prop.getName(), "Loaded"))
.returns(TypeName.BOOLEAN)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(ApiIgnore.class)
.addAnnotation(JsonIgnore.class)
.addStatement("return this.$L", stateFieldName);
typeBuilder.addMethod(isLoadedBuilder.build());
MethodSpec.Builder setLoadedBuilder = MethodSpec
.methodBuilder(StringUtil.identifier("set", prop.getName(), "Loaded"))
.addParameter(TypeName.BOOLEAN, "loaded")
.addStatement("this.$L = loaded", stateFieldName);
typeBuilder.addMethod(setLoadedBuilder.build());
}
}
private void addDefaultConstructor() {
MethodSpec.Builder builder = MethodSpec
.constructorBuilder()
.addModifiers(Modifier.PUBLIC);
typeBuilder.addMethod(builder.build());
}
private void addConverterConstructor() {
MethodSpec.Builder builder = MethodSpec
.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(
ParameterSpec
.builder(dtoType.getBaseType().getClassName(), "base")
.addAnnotation(NotNull.class)
.build()
);
for (DtoProp prop : dtoType.getDtoProps()) {
if (isSimpleProp(prop)) {
if (prop.isNullable()) {
builder.addStatement(
"this.$L = (($T)base).__isLoaded($T.byIndex($T.$L)) ? base.$L() : null",
prop.getName(),
ImmutableSpi.class,
org.babyfish.jimmer.apt.immutable.generator.Constants.PROP_ID_CLASS_NAME,
dtoType.getBaseType().getProducerClassName(),
prop.getBaseProp().getSlotName(),
prop.getBaseProp().getGetterName()
);
} else {
builder.addStatement(
"this.$L = base.$L()",
prop.getName(),
prop.getBaseProp().getGetterName()
);
}
} else {
if (!prop.isNullable() && prop.isBaseNullable()) {
builder.addStatement(
"this.$L = $L.get($>\n" +
"base,\n" +
"$S\n" +
"$<)",
prop.getName(),
StringUtil.snake(prop.getName() + "Accessor", StringUtil.SnakeCase.UPPER),
"Cannot convert \"" +
dtoType.getBaseType().getClassName() +
"\" to " +
"\"" +
getDtoClassName() +
"\" because the cannot get non-null " +
"value for \"" +
prop.getName() +
"\""
);
} else {
builder.addStatement(
"this.$L = $L.get(base)",
prop.getName(),
StringUtil.snake(prop.getName() + "Accessor", StringUtil.SnakeCase.UPPER)
);
}
}
}
typeBuilder.addMethod(builder.build());
}
private void addToEntity() {
MethodSpec.Builder builder = MethodSpec
.methodBuilder(dtoType.getBaseType().isEntity() ? "toEntity" : "toImmutable")
.addModifiers(Modifier.PUBLIC)
.returns(dtoType.getBaseType().getClassName())
.addAnnotation(Override.class);
builder.addCode(
"return $T.$L.produce(__draft -> {$>\n",
dtoType.getBaseType().getDraftClassName(),
"$"
);
for (DtoProp prop : dtoType.getDtoProps()) {
if (prop.getBaseProp().isJavaFormula()) {
continue;
}
String stateFieldName = stateFieldName(prop, false);
if (stateFieldName != null) {
builder.beginControlFlow("if ($L)", stateFieldName);
}
if (isSimpleProp(prop)) {
builder.addStatement("__draft.$L($L)", prop.getBaseProp().getSetterName(), prop.getName());
} else {
ImmutableProp tailBaseProp = prop.toTailProp().getBaseProp();
if (tailBaseProp.isList() && tailBaseProp.isAssociation(true)) {
builder.addStatement(
"$L.set(__draft, $L != null ? $L : $T.emptyList())",
StringUtil.snake(prop.getName() + "Accessor", StringUtil.SnakeCase.UPPER),
prop.getName(),
prop.getName(),
org.babyfish.jimmer.apt.immutable.generator.Constants.COLLECTIONS_CLASS_NAME
);
} else {
builder.addStatement(
"$L.set(__draft, $L)",
StringUtil.snake(prop.getName() + "Accessor", StringUtil.SnakeCase.UPPER),
prop.getName()
);
}
}
if (stateFieldName != null) {
builder.endControlFlow();
}
}
builder.addCode("$<});\n");
typeBuilder.addMethod(builder.build());
}
private void addEntityType() {
MethodSpec.Builder builder = MethodSpec
.methodBuilder("entityType")
.returns(
ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.CLASS_CLASS_NAME,
dtoType.getBaseType().getClassName()
)
)
.addModifiers(Modifier.PUBLIC)
.addStatement("return $T.class", dtoType.getBaseType().getClassName());
if (isImpl()) {
builder.addAnnotation(Override.class);
}
typeBuilder.addMethod(builder.build());
}
private void addApplyTo() {
MethodSpec.Builder builder = MethodSpec
.methodBuilder("applyTo")
.addModifiers(Modifier.PUBLIC);
if (isImpl()) {
builder.addAnnotation(Override.class)
.addParameter(
ParameterSpec.builder(
ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.SPECIFICATION_ARGS_CLASS_NAME,
dtoType.getBaseType().getClassName(),
dtoType.getBaseType().getTableClassName()
),
"args"
).build()
);
} else {
builder.addParameter(
ParameterSpec
.builder(
org.babyfish.jimmer.apt.immutable.generator.Constants.PREDICATE_APPLIER_CLASS_NAME,
"__applier"
)
.build()
);
}
List stack = Collections.emptyList();
if (isImpl()) {
builder.addStatement(
"$T __applier = args.getApplier()",
org.babyfish.jimmer.apt.immutable.generator.Constants.PREDICATE_APPLIER_CLASS_NAME
);
}
for (DtoProp prop : dtoType.getDtoProps()) {
List newStack = new ArrayList<>(stack.size() + 2);
DtoProp tailProp = prop.toTailProp();
for (DtoProp p = prop; p != null; p = p.getNextProp()) {
if (p != tailProp || p.getTargetType() != null) {
newStack.add(p.getBaseProp());
}
}
stack = addStackOperations(builder, stack, newStack);
addPredicateOperation(builder, prop);
}
addStackOperations(builder, stack, Collections.emptyList());
typeBuilder.addMethod(builder.build());
}
private List addStackOperations(
MethodSpec.Builder builder,
List stack,
List newStack
) {
int size = Math.min(stack.size(), newStack.size());
int sameCount = size;
for (int i = 0; i < size; i++) {
if (stack.get(i) != newStack.get(i)) {
sameCount = i;
break;
}
}
for (int i = stack.size() - sameCount; i > 0; --i) {
builder.addStatement("__applier.pop()");
}
for (ImmutableProp prop : newStack.subList(sameCount, newStack.size())) {
builder.addStatement(
"__applier.push($T.$L.unwrap())",
prop.getDeclaringType().getPropsClassName(),
StringUtil.snake(prop.getName(), StringUtil.SnakeCase.UPPER)
);
}
return newStack;
}
private void addPredicateOperation(MethodSpec.Builder builder, DtoProp prop) {
String propName = prop.getName();
DtoProp tailProp = prop.toTailProp();
if (tailProp.getTargetType() != null) {
builder.beginControlFlow("if (this.$L != null)", propName);
if (tailProp.getTargetType().getBaseType().isEntity()) {
builder.addStatement("this.$L.applyTo(args.child())", propName);
} else {
builder.addStatement("this.$L.applyTo(args.getApplier())", propName);
}
builder.endControlFlow();
return;
}
String funcName = tailProp.getFuncName();
String javaMethodName = funcName;
if (funcName == null) {
funcName = "eq";
javaMethodName = "eq";
} else if ("null".equals(funcName)) {
javaMethodName = "isNull";
} else if ("notNull".equals(funcName)) {
javaMethodName = "isNotNull";
} else if ("id".equals(funcName)) {
funcName = "associatedIdEq";
javaMethodName = "associatedIdEq";
}
CodeBlock.Builder cb = CodeBlock.builder();
if (org.babyfish.jimmer.dto.compiler.Constants.MULTI_ARGS_FUNC_NAMES.contains(funcName)) {
cb.add("__applier.$L(new $T[] { ", javaMethodName, org.babyfish.jimmer.apt.immutable.generator.Constants.IMMUTABLE_PROP_CLASS_NAME);
boolean addComma = false;
for (ImmutableProp baseProp : tailProp.getBasePropMap().values()) {
if (addComma) {
cb.add(", ");
} else {
addComma = true;
}
cb.add(
"$T.$L.unwrap()",
baseProp.getDeclaringType().getPropsClassName(),
StringUtil.snake(baseProp.getName(), StringUtil.SnakeCase.UPPER)
);
}
cb.add(" }, ");
} else {
cb.add(
"__applier.$L($T.$L.unwrap(), ",
funcName,
tailProp.getBaseProp().getDeclaringType().getPropsClassName(),
StringUtil.snake(tailProp.getBaseProp().getName(), StringUtil.SnakeCase.UPPER)
);
}
if (isSpecificationConverterRequired(tailProp)) {
cb.add(
"$L(this.$L)",
StringUtil.identifier("__convert", propName),
propName
);
} else {
cb.add("this.$L", propName);
}
if ("like".equals(funcName) || "notLike".equals(funcName)) {
cb.add(", ");
cb.add(tailProp.getLikeOptions().contains(LikeOption.INSENSITIVE) ? "true" : "false");
cb.add(", ");
cb.add(tailProp.getLikeOptions().contains(LikeOption.MATCH_START) ? "true" : "false");
cb.add(", ");
cb.add(tailProp.getLikeOptions().contains(LikeOption.MATCH_END) ? "true" : "false");
}
cb.addStatement(")");
builder.addCode(cb.build());
}
private void addSpecificationConverter(DtoProp prop) {
if (!isSpecificationConverterRequired(prop)) {
return;
}
ImmutableProp baseProp = prop.toTailProp().getBaseProp();
TypeName baseTypeName = null;
String funcName = prop.getFuncName();
if (funcName != null) {
switch (funcName) {
case "id":
baseTypeName = baseProp.getTargetType().getIdProp().getTypeName();
if (baseProp.isList() && !dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
baseTypeName = ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.LIST_CLASS_NAME,
baseTypeName.box()
);
}
break;
case "valueIn":
case "valueNotIn":
baseTypeName = ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.LIST_CLASS_NAME,
baseProp.getTypeName().box()
);
break;
case "associatedIdEq":
case "associatedIdNe":
baseTypeName = baseProp.getTargetType().getIdProp().getTypeName();
break;
case "associatedIdIn":
case "associatedIdNotIn":
baseTypeName = ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.LIST_CLASS_NAME,
baseProp.getTargetType().getIdProp().getTypeName().box()
);
}
}
if (baseTypeName == null) {
baseTypeName = baseProp.getTypeName();
}
baseTypeName = baseTypeName.box();
MethodSpec.Builder builder = MethodSpec
.methodBuilder(StringUtil.identifier("__convert", prop.getName()))
.addModifiers(Modifier.PRIVATE)
.addParameter(getPropTypeName(prop), "value")
.returns(baseTypeName);
CodeBlock.Builder cb = CodeBlock.builder();
cb.beginControlFlow("if ($L == null)", prop.getName());
cb.addStatement("return null");
cb.endControlFlow();
if (prop.getEnumType() != null) {
addValueToEnum(cb, prop, "value");
} else {
cb.addStatement(
"return $T.$L.unwrap().<$T, $T>$L.input(value)",
dtoType.getBaseType().getPropsClassName(),
StringUtil.snake(baseProp.getName(), StringUtil.SnakeCase.UPPER),
baseTypeName,
getPropTypeName(prop).box(),
baseProp.isAssociation(true) ?
"getAssociatedIdConverter(true)" :
"getConverter(" + (prop.isFunc("valueIn", "valueNotIn") ? "true" : "") + ")"
);
}
builder.addCode(cb.build());
typeBuilder.addMethod(builder.build());
}
private void addHibernateValidatorEnhancement(boolean getter) {
String methodName = "$$_hibernateValidator_get" +
(getter ? "Getter" : "Field") +
"Value";
MethodSpec.Builder builder = MethodSpec
.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(org.babyfish.jimmer.apt.immutable.generator.Constants.STRING_CLASS_NAME, "name")
.returns(TypeName.OBJECT)
.beginControlFlow("switch (name)");
for (AbstractProp prop : dtoType.getProps()) {
builder.addStatement(
"case $S: return $L",
getter ?
StringUtil.identifier(
getPropTypeName(prop) == TypeName.BOOLEAN ? "is" : "get",
prop.getName()
) :
prop.getName(),
prop.getName()
);
}
builder
.addStatement(
"default: throw new IllegalArgumentException($S + name + $S)",
"No " + (getter ? "getter" : "field") + " named \"",
"\""
)
.endControlFlow();
typeBuilder.addMethod(builder.build());
}
@SuppressWarnings("unchecked")
public TypeName getPropTypeName(AbstractProp prop) {
if (prop instanceof DtoProp, ?>) {
return getPropTypeName((DtoProp) prop);
}
return getTypeName(((UserProp)prop).getTypeRef());
}
private TypeName getPropTypeName(DtoProp prop) {
ImmutableProp baseProp = prop.toTailProp().getBaseProp();
EnumType enumType = prop.getEnumType();
if (enumType != null) {
if (enumType.isNumeric()) {
return prop.isNullable() ? TypeName.INT.box() : TypeName.INT;
}
return org.babyfish.jimmer.apt.immutable.generator.Constants.STRING_CLASS_NAME;
}
ConverterMetadata metadata = converterMetadataOf(prop);
if (dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
String funcName = prop.toTailProp().getFuncName();
if (funcName != null) {
switch (funcName) {
case "null":
case "notNull":
return TypeName.BOOLEAN;
case "valueIn":
case "valueNotIn":
return ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.COLLECTION_CLASS_NAME,
metadata != null ?
metadata.getTargetTypeName() :
toListType(
getPropElementName(prop),
baseProp.isList()
)
);
case "id":
case "associatedIdEq":
case "associatedIdNe":
final TypeName clientTypeName = baseProp.getTargetType().getIdProp().getClientTypeName();
if (prop.isNullable()) {
return clientTypeName.box();
}
return clientTypeName;
case "associatedIdIn":
case "associatedIdNotIn":
return ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.COLLECTION_CLASS_NAME,
baseProp.getTargetType().getIdProp().getClientTypeName().box()
);
}
}
if (baseProp.isAssociation(true)) {
return getPropElementName(prop);
}
}
if (metadata != null) {
return metadata.getTargetTypeName();
}
return toListType(getPropElementName(prop), baseProp.isList() && !baseProp.isFormula());
}
private static TypeName toListType(TypeName typeName, boolean isList) {
return isList ?
ParameterizedTypeName.get(
org.babyfish.jimmer.apt.immutable.generator.Constants.LIST_CLASS_NAME,
typeName.box()
) :
typeName;
}
private static TypeName getTypeName(@Nullable TypeRef typeRef) {
return getTypeName(typeRef, false);
}
private static TypeName getTypeName(@Nullable TypeRef typeRef, boolean toBoxType) {
if (typeRef == null) {
return WildcardTypeName.subtypeOf(TypeName.OBJECT);
}
TypeName typeName;
switch (typeRef.getTypeName()) {
case "Boolean":
typeName = toBoxType || typeRef.isNullable() ? TypeName.BOOLEAN.box() : TypeName.BOOLEAN;
break;
case "Char":
typeName = toBoxType || typeRef.isNullable() ? TypeName.CHAR.box() : TypeName.CHAR;
break;
case "Byte":
typeName = toBoxType || typeRef.isNullable() ? TypeName.BYTE.box() : TypeName.BYTE;
break;
case "Short":
typeName = toBoxType || typeRef.isNullable() ? TypeName.SHORT.box() : TypeName.SHORT;
break;
case "Int":
typeName = toBoxType || typeRef.isNullable() ? TypeName.INT.box() : TypeName.INT;
break;
case "Long":
typeName = toBoxType || typeRef.isNullable() ? TypeName.LONG.box() : TypeName.LONG;
break;
case "Float":
typeName = toBoxType || typeRef.isNullable() ? TypeName.FLOAT.box() : TypeName.FLOAT;
break;
case "Double":
typeName = toBoxType || typeRef.isNullable() ? TypeName.DOUBLE.box() : TypeName.DOUBLE;
break;
case "Any":
typeName = TypeName.OBJECT;
break;
case "String":
typeName = org.babyfish.jimmer.apt.immutable.generator.Constants.STRING_CLASS_NAME;
break;
case "Array":
typeName = ArrayTypeName.of(
typeRef.getArguments().get(0).getTypeRef() == null ?
TypeName.OBJECT :
getTypeName(typeRef.getArguments().get(0).getTypeRef(), false)
);
break;
case "Iterable":
case "MutableIterable":
typeName = ClassName.get(Iterable.class);
break;
case "Collection":
case "MutableCollection":
typeName = ClassName.get(Collection.class);
break;
case "List":
case "MutableList":
typeName = ClassName.get(List.class);
break;
case "Set":
case "MutableSet":
typeName = ClassName.get(Set.class);
break;
case "Map":
case "MutableMap":
typeName = ClassName.get(Map.class);
break;
default:
typeName = ClassName.bestGuess(typeRef.getTypeName());
break;
}
int argCount = typeRef.getArguments().size();
if (argCount == 0 || typeName instanceof ArrayTypeName) {
return typeName;
}
TypeName[] argTypeNames = new TypeName[argCount];
for (int i = 0; i < argCount; i++) {
TypeRef.Argument arg = typeRef.getArguments().get(i);
TypeName argTypeName = getTypeName(arg.getTypeRef(), true);
if (arg.isIn()) {
argTypeName = WildcardTypeName.supertypeOf(argTypeName);
} else if (arg.getTypeRef() != null && (arg.isOut() || isForceOut(typeRef.getTypeName()))) {
argTypeName = WildcardTypeName.subtypeOf(argTypeName);
}
argTypeNames[i] = argTypeName;
}
return ParameterizedTypeName.get(
(ClassName) typeName,
argTypeNames
);
}
private void addHashCode() {
MethodSpec.Builder builder = MethodSpec
.methodBuilder("hashCode")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(TypeName.INT);
boolean first = true;
for (DtoProp prop : dtoType.getDtoProps()) {
CodeBlock.Builder cb = CodeBlock.builder();
if (first) {
cb.add("int hash = ");
first = false;
} else {
cb.add("hash = hash * 31 + ");
}
TypeName typeName = getPropTypeName(prop);
if (typeName.isPrimitive()) {
cb.add(
"$T.hashCode($L)",
typeName.box(),
prop.getName().equals("hash") ? "this." + prop.getName() : prop.getName()
);
} else if (typeName instanceof ArrayTypeName) {
cb.add("$T.hashCode($L)", Arrays.class, prop.getName());
} else {
cb.add("$T.hashCode($L)", Objects.class, prop.getName());
}
builder.addStatement(cb.build());
String stateFieldName = stateFieldName(prop, false);
if (stateFieldName != null) {
builder.addStatement("hash = hash * 31 + Boolean.hashCode($L)", stateFieldName);
}
}
for (UserProp prop : dtoType.getUserProps()) {
CodeBlock.Builder cb = CodeBlock.builder();
if (first) {
cb.add("int hash = ");
first = false;
} else {
cb.add("hash = hash * 31 + ");
}
TypeName typeName = getTypeName(prop.getTypeRef());
if (typeName.isPrimitive()) {
cb.add(
"$T.hashCode($L)",
typeName.box(),
prop.getAlias().equals("hash") ? "this." + prop.getAlias() : prop.getAlias()
);
} else if (typeName instanceof ArrayTypeName) {
cb.add("$T.hashCode($L)", Arrays.class, prop.getName());
} else {
cb.add("$T.hashCode($L)", Objects.class, prop.getAlias());
}
builder.addStatement(cb.build());
}
builder.addStatement(first ? "return 0" : "return hash");
typeBuilder.addMethod(builder.build());
}
private void addEquals() {
MethodSpec.Builder builder = MethodSpec
.methodBuilder("equals")
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.OBJECT, "o")
.addAnnotation(Override.class)
.returns(TypeName.BOOLEAN);
builder.beginControlFlow("if (o == null || this.getClass() != o.getClass())")
.addStatement("return false")
.endControlFlow();
builder.addStatement("$L other = ($L) o", getSimpleName(), getSimpleName());
for (DtoProp prop : dtoType.getDtoProps()) {
String propName = prop.getName();
String stateFieldName = stateFieldName(prop, false);
if (stateFieldName != null) {
builder.beginControlFlow("if ($L != other.$L)", stateFieldName, stateFieldName);
builder.addStatement("return false");
builder.endControlFlow();
}
String thisProp = propName.equals("o") || propName.equals("other") ? "this" + propName : propName;
TypeName typeName = getPropTypeName(prop);
if (stateFieldName != null) {
if (typeName.isPrimitive()) {
builder.beginControlFlow(
"if ($L && $L != other.$L)",
stateFieldName,
thisProp,
propName
);
} else {
builder.beginControlFlow(
"if ($L && !$T.equals($L, other.$L))",
stateFieldName,
Objects.class,
thisProp,
propName
);
}
} else {
if (typeName.isPrimitive()) {
builder.beginControlFlow("if ($L != other.$L)", thisProp, propName);
} else if (typeName instanceof ArrayTypeName) {
builder.beginControlFlow("if (!$T.equals($L, other.$L))", Arrays.class, thisProp, propName);
} else {
builder.beginControlFlow("if (!$T.equals($L, other.$L))", Objects.class, thisProp, propName);
}
}
builder.addStatement("return false");
builder.endControlFlow();
}
for (UserProp prop : dtoType.getUserProps()) {
String propName = prop.getAlias();
String thisProp = propName.equals("o") || propName.equals("other") ? "this" + propName : propName;
TypeName typeName = getTypeName(prop.getTypeRef());
if (typeName.isPrimitive()) {
builder.beginControlFlow("if ($L != other.$L)", thisProp, propName);
} else if (typeName instanceof ArrayTypeName) {
builder.beginControlFlow("if (!$T.equals($L, other.$L))", Arrays.class, thisProp, propName);
} else {
builder.beginControlFlow("if (!$T.equals($L, other.$L))", Objects.class, thisProp, propName);
}
builder.addStatement("return false");
builder.endControlFlow();
}
builder.addStatement("return true");
typeBuilder.addMethod(builder.build());
}
private void addToString() {
MethodSpec.Builder builder = MethodSpec
.methodBuilder("toString")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.returns(org.babyfish.jimmer.apt.immutable.generator.Constants.STRING_CLASS_NAME);
builder.addStatement("StringBuilder builder = new StringBuilder()");
builder.addStatement("builder.append($S).append('(')", simpleNamePath());
String separator = "";
for (DtoProp prop : dtoType.getDtoProps()) {
String stateFieldName = stateFieldName(prop, false);
if (stateFieldName != null) {
builder.beginControlFlow("if ($L)", stateFieldName);
} else if (prop.getInputModifier() == DtoModifier.FUZZY) {
builder.beginControlFlow("if ($L != null)", prop.getName());
}
if (prop.getName().equals("builder")) {
builder.addStatement("builder.append($S).append(this.$L)", separator + prop.getName() + '=', prop.getName());
} else {
builder.addStatement("builder.append($S).append($L)", separator + prop.getName() + '=', prop.getName());
}
if (stateFieldName != null || prop.getInputModifier() == DtoModifier.FUZZY) {
builder.endControlFlow();
}
separator = ", ";
}
for (UserProp prop : dtoType.getUserProps()) {
if (prop.getAlias().equals("builder")) {
builder.addStatement("builder.append($S).append(this.$L)", separator + prop.getAlias() + '=', prop.getAlias());
} else {
builder.addStatement("builder.append($S).append($L)", separator + prop.getAlias() + '=', prop.getAlias());
}
separator = ", ";
}
builder.addStatement("builder.append(')')");
builder.addStatement("return builder.toString()");
typeBuilder.addMethod(builder.build());
}
private String simpleNamePath() {
String name = getSimpleName();
if (parent != null) {
return parent.simpleNamePath() + '.' + name;
}
return name;
}
public TypeName getPropElementName(DtoProp prop) {
DtoProp tailProp = prop.toTailProp();
DtoType targetType = tailProp.getTargetType();
if (targetType != null) {
if (tailProp.isRecursive() && !targetType.isFocusedRecursion()) {
return getDtoClassName();
}
if (targetType.getName() == null) {
List list = new ArrayList<>();
collectNames(list);
if (!tailProp.isRecursive() || targetType.isFocusedRecursion()) {
list.add(targetSimpleName(tailProp));
}
return ClassName.get(
root.dtoType.getPackageName(),
list.get(0),
list.subList(1, list.size()).toArray(EMPTY_STR_ARR)
);
}
return ClassName.get(
root.dtoType.getPackageName(),
targetType.getName()
);
}
ImmutableProp baseProp = tailProp.getBaseProp();
TypeName typeName;
if (tailProp.isIdOnly()) {
typeName = tailProp.getBaseProp().getTargetType().getIdProp().getTypeName();
} else if (baseProp.getIdViewBaseProp() != null) {
typeName = baseProp.getIdViewBaseProp().getTargetType().getIdProp().getClientTypeName();
} else {
typeName = tailProp.getBaseProp().getClientTypeName();
}
if (typeName.isPrimitive() && prop.isNullable()) {
return typeName.box();
}
return typeName;
}
private void collectNames(List list) {
if (parent == null) {
list.add(dtoType.getName());
} else {
parent.collectNames(list);
list.add(innerClassName);
}
}
private String targetSimpleName(DtoProp prop) {
DtoType targetType = prop.getTargetType();
if (targetType == null) {
throw new IllegalArgumentException("prop is not association");
}
if (targetType.getName() != null) {
return targetType.getName();
}
if (prop.isRecursive() && !targetType.isFocusedRecursion()) {
return innerClassName != null ? innerClassName : dtoType.getName();
}
return standardTargetSimpleName("TargetOf_" + prop.getName());
}
private String standardTargetSimpleName(String targetSimpleName) {
boolean conflict = false;
for (DtoGenerator generator = this; generator != null; generator = generator.parent) {
if (generator.getSimpleName().equals(targetSimpleName)) {
conflict = true;
break;
}
}
if (!conflict) {
return targetSimpleName;
}
for (int i = 2; i < 100; i++) {
conflict = false;
String newTargetSimpleName = targetSimpleName + '_' + i;
for (DtoGenerator generator = this; generator != null; generator = generator.parent) {
if (generator.getSimpleName().equals(newTargetSimpleName)) {
conflict = true;
break;
}
}
if (!conflict) {
return newTargetSimpleName;
}
}
throw new AssertionError("Dto is too deep");
}
private boolean isSpecificationConverterRequired(DtoProp prop) {
if (!dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
return false;
}
return prop.getEnumType() != null || converterMetadataOf(prop) != null;
}
private ConverterMetadata converterMetadataOf(DtoProp prop) {
ImmutableProp baseProp = prop.toTailProp().getBaseProp();
ConverterMetadata metadata = baseProp.getConverterMetadata();
if (metadata != null) {
return metadata;
}
String funcName = prop.getFuncName();
if ("id".equals(funcName)) {
metadata = baseProp.getTargetType().getIdProp().getConverterMetadata();
if (metadata != null && baseProp.isList() && !dtoType.getModifiers().contains(DtoModifier.SPECIFICATION)) {
metadata = metadata.toListMetadata(baseProp.context());
}
return metadata;
}
if ("associatedInEq".equals(funcName) || "associatedInNe".equals(funcName)) {
return baseProp.getTargetType().getIdProp().getConverterMetadata();
}
if ("associatedIdIn".equals(funcName) || "associatedIdNotIn".equals(funcName)) {
metadata = baseProp.getTargetType().getIdProp().getConverterMetadata();
if (metadata != null) {
return metadata.toListMetadata(baseProp.context());
}
}
if (baseProp.getIdViewBaseProp() != null) {
metadata = baseProp.getIdViewBaseProp().getTargetType().getIdProp().getConverterMetadata();
if (metadata != null) {
return baseProp.isList() ? metadata.toListMetadata(baseProp.context()) : metadata;
}
}
return null;
}
private boolean hasElementType(Anno anno, ElementType elementType) {
TypeElement annoElement = ctx.getElements().getTypeElement(anno.getQualifiedName());
if (annoElement == null) {
throw new DtoException(
"Cannot find the annotation declaration whose type is \"" +
anno.getQualifiedName() +
"\""
);
}
Target target = annoElement.getAnnotation(Target.class);
if (target != null) {
for (ElementType et : target.value()) {
if (et == elementType) {
return true;
}
}
}
return false;
}
private static boolean isCopyableAnnotation(
AnnotationMirror annotationMirror,
Collection dtoAnnotations,
Boolean forMethod
) {
String qualifiedName = ((TypeElement) annotationMirror.getAnnotationType().asElement()).getQualifiedName().toString();
if (qualifiedName.startsWith("org.babyfish.jimmer.") &&
!qualifiedName.startsWith("org.babyfish.jimmer.client.")) {
return false;
}
if (isNullityAnnotation(qualifiedName)) {
return false;
}
if (forMethod != null) {
boolean accept = false;
Target target = annotationMirror.getAnnotationType().asElement().getAnnotation(Target.class);
if (target != null) {
if (Arrays.stream(target.value()).anyMatch(it -> it == ElementType.METHOD)) {
accept = forMethod;
} else if (!forMethod) {
accept = Arrays.stream(target.value()).anyMatch(it -> it == ElementType.FIELD);
}
}
if (!accept) {
return false;
}
}
for (Anno dtoAnno : dtoAnnotations) {
if (dtoAnno.getQualifiedName().endsWith(qualifiedName)) {
return false;
}
}
return true;
}
private static boolean isNullityAnnotation(String qualifiedName) {
int lastDotIndex = qualifiedName.lastIndexOf('.');
if (lastDotIndex != -1) {
qualifiedName = qualifiedName.substring(lastDotIndex + 1);
}
switch (qualifiedName) {
case "Null":
case "Nullable":
case "NotNull":
case "NonNull":
return true;
default:
return false;
}
}
private static AnnotationSpec annotationOf(Anno anno) {
AnnotationSpec.Builder builder = AnnotationSpec
.builder(ClassName.bestGuess(anno.getQualifiedName()));
for (Map.Entry e : anno.getValueMap().entrySet()) {
String name = e.getKey();
Anno.Value value = e.getValue();
builder.addMember(name, codeBlockOf(value));
}
return builder.build();
}
private static CodeBlock codeBlockOf(Anno.Value value) {
CodeBlock.Builder builder = CodeBlock.builder();
if (value instanceof Anno.ArrayValue) {
builder.add("{\n$>");
boolean addSeparator = false;
for (Anno.Value element : ((Anno.ArrayValue)value).elements) {
if (addSeparator) {
builder.add(", \n");
} else {
addSeparator = true;
}
builder.add("$L", codeBlockOf(element));
}
builder.add("$<\n}");
} else if (value instanceof Anno.AnnoValue) {
builder.add("$L", annotationOf(((Anno.AnnoValue)value).anno));
} else if(value instanceof Anno.TypeRefValue) {
builder.add("$T.class", getTypeName(((Anno.TypeRefValue)value).typeRef));
} else if (value instanceof Anno.EnumValue) {
builder.add(
"$T.$L",
ClassName.bestGuess(((Anno.EnumValue)value).qualifiedName),
((Anno.EnumValue)value).constant
);
} else if (value instanceof Anno.LiteralValue) {
builder.add(((Anno.LiteralValue)value).value);
}
return builder.build();
}
private static boolean isForceOut(String typeName) {
switch (typeName) {
case "Iterable":
case "Collection":
case "List":
case "Set":
case "Map":
return true;
default:
return false;
}
}
@SuppressWarnings("unchecked")
private String getterName(AbstractProp prop) {
TypeName typeName = prop instanceof DtoProp, ?> ?
getPropTypeName((DtoProp) prop) :
getTypeName(((UserProp)prop).getTypeRef());
String suffix = prop instanceof DtoProp, ?> ?
prop.getName() :
prop.getAlias();
if (suffix.startsWith("is") &&
suffix.length() > 2 &&
Character.isUpperCase(suffix.charAt(2)) &&
typeName.equals(TypeName.BOOLEAN)) {
suffix = suffix.substring(2);
}
return StringUtil.identifier(
typeName.equals(TypeName.BOOLEAN) ? "is" : "get",
suffix
);
}
@SuppressWarnings("unchecked")
private String setterName(AbstractProp prop) {
TypeName typeName = prop instanceof DtoProp, ?> ?
getPropTypeName((DtoProp) prop) :
getTypeName(((UserProp)prop).getTypeRef());
String suffix = prop instanceof DtoProp, ?> ?
((DtoProp)prop).getName() :
prop.getAlias();
if (suffix.startsWith("is") &&
suffix.length() > 2 &&
Character.isUpperCase(suffix.charAt(2)) &&
typeName.equals(TypeName.BOOLEAN)) {
suffix = suffix.substring(2);
}
return StringUtil.identifier("set", suffix);
}
private class Document {
private final Context ctx;
private final Doc dtoTypeDoc;
private final Doc baseTypeDoc;
private String result;
public Document(Context ctx, DtoType dtoType) {
this.ctx = ctx;
dtoTypeDoc = Doc.parse(dtoType.getDoc());
baseTypeDoc = Doc.parse(ctx.getElements().getDocComment(dtoType.getBaseType().getTypeElement()));
}
public String get() {
String ret = result;
if (ret == null) {
if (dtoTypeDoc != null) {
ret = dtoTypeDoc.toString();
} else if (baseTypeDoc != null) {
ret = baseTypeDoc.toString();
} else {
ret = "";
}
ret = ret.replace("$", "$$");
this.result = ret;
}
return ret.isEmpty() ? null : ret;
}
public String get(AbstractProp prop) {
String value = getImpl(prop);
if (value == null) {
return null;
}
return value.replace("$", "$$");
}
@SuppressWarnings("unchecked")
private String getImpl(AbstractProp prop) {
ImmutableProp baseProp;
if (prop instanceof DtoProp, ?>) {
baseProp = ((DtoProp, ImmutableProp>) prop).getBaseProp();
} else {
baseProp = null;
}
if (prop.getDoc() != null) {
Doc doc = Doc.parse(prop.getDoc());
if (doc != null) {
return doc.toString();
}
}
if (dtoTypeDoc != null) {
String name = prop.getAlias();
if (name == null) {
assert baseProp != null;
name = baseProp.getName();
}
String doc = dtoTypeDoc.getParameterValueMap().get(name);
if (doc != null) {
return doc;
}
}
if (baseProp != null) {
Doc doc = Doc.parse(ctx.getElements().getDocComment(baseProp.toElement()));
if (doc != null) {
return doc.toString();
}
}
if (baseTypeDoc != null && baseProp != null) {
String doc = baseTypeDoc.getParameterValueMap().get(baseProp.getName());
if (doc != null) {
return doc;
}
}
return null;
}
}
private boolean isImpl() {
return dtoType.getBaseType().isEntity() ||
!dtoType.getModifiers().contains(DtoModifier.SPECIFICATION);
}
TypeSpec.Builder getTypeBuilder() {
return typeBuilder;
}
@Nullable
String stateFieldName(AbstractProp prop, boolean builder) {
if (!prop.isNullable()) {
return null;
}
if (!(prop instanceof DtoProp, ?>)) {
return null;
}
if (!dtoType.getModifiers().contains(DtoModifier.INPUT)) {
return null;
}
DtoModifier modifier = ((DtoProp, ?>) prop).getInputModifier();
if (modifier == DtoModifier.FIXED && !builder) {
return null;
}
if (modifier == DtoModifier.STATIC || modifier == DtoModifier.FUZZY) {
return null;
}
return StringUtil.identifier("_is", prop.getName(), "Loaded");
}
private static boolean isFieldNullable(AbstractProp prop) {
if (prop instanceof DtoProp, ?>) {
String funcName = ((DtoProp, ?>) prop).getFuncName();
return !"null".equals(funcName) && !"notNull".equals(funcName);
}
return true;
}
private boolean isBuildRequired() {
if (!dtoType.getModifiers().contains(DtoModifier.INPUT)) {
return false;
}
for (DtoProp, ?> prop : dtoType.getDtoProps()) {
DtoModifier inputModifier = prop.getInputModifier();
if (inputModifier == DtoModifier.FIXED || inputModifier == DtoModifier.DYNAMIC) {
return true;
}
}
return false;
}
private boolean isHibernateValidatorEnhancementRequired() {
return ctx.isHibernateValidatorEnhancement() &&
dtoType.getDtoProps().stream().anyMatch(
it -> it.getInputModifier() == DtoModifier.DYNAMIC
);
}
}