Skip to content

Commit

Permalink
Add support for Modifiable to extend an inner class/interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Mitch Halpin authored and Mitch Halpin committed Jan 19, 2020
1 parent f30ef44 commit 67f8083
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 47 deletions.
6 changes: 6 additions & 0 deletions value-annotations/src/org/immutables/value/Value.java
Expand Up @@ -663,6 +663,12 @@
*/
String typeModifiable() default "Modifiable*";

/**
* Inner builder class name which will be matched to be extend/super for generated Modifiable class.
* @return naming template
*/
String typeInnerModifiable() default "Modifiable";

/**
* Generated "with" interface name. Used to detect a demand and generate "with" interface.
* @return naming template
Expand Down
Expand Up @@ -65,9 +65,17 @@ Use @Value.Modifiable cannot be used with @Value.Immutable which implements Ordi
LongPositions positions = longsFor mandatories,
LongPositions nondefaultsPositions = longsFor nondefaults]
[type.typeImmutable.access][if not topLevel]static [/if][if type.names.create ne 'new']final [/if][output.linesShortable]class [type.names.typeModifiable][type.generics]
[if type.innerModifiable.isSuper]
[if type.innerModifiable.isInterface]
extends [type.typeAbstract] implements [type.typeAbstract.relativeRaw].[type.innerModifiable.simpleName][type.innerModifiable.generics.args][if type.serial.shouldImplement], java.io.Serializable[/if] {
[else]
extends [type.typeAbstract.relativeRaw].[type.innerModifiable.simpleName][type.innerModifiable.generics.args][if type.serial.shouldImplement] implements java.io.Serializable[/if] {
[/if]
[else]
[if type.implementing]implements[else]extends[/if] [type.typeAbstract][if type.serial.shouldImplement][if type.implementing],
[else]
implements [/if]java.io.Serializable[/if] {[/output.linesShortable]
implements [/if]java.io.Serializable[/if] {
[/if][/output.linesShortable]
[if type.serial.enabled]
[for serialVersion = type.serialVersionUID]
[if serialVersion]
Expand Down
Expand Up @@ -684,7 +684,73 @@ public InnerBuilderDefinition innerBuilder() {
return new InnerBuilderDefinition();
}

public final class InnerBuilderDefinition {
@Value.Lazy
public InnerModifiableDefinition innerModifiable() {
return new InnerModifiableDefinition();
}

public final class InnerBuilderDefinition extends InnerBaseClassDefinition {
public InnerBuilderDefinition() {
super(names().namings.typeInnerBuilder);
}

protected boolean isExtending(TypeElement element) {
if (element.getKind() == ElementKind.CLASS) {
String superclassString = SourceExtraction.getSuperclassString(element);
String rawSuperclass = SourceTypes.extract(superclassString).getKey();
// If we are extending yet to be generated builder, we detect it by having the same name
// as relative name of builder type
return rawSuperclass.endsWith(typeImplementationBuilder().relativeRaw());
}
return false;
}

protected void lateValidateExtending(TypeElement t) {
super.lateValidateExtending(t);

if (protoclass().styles().style().stagedBuilder()) {
protoclass()
.report()
.withElement(t)
.warning(About.INCOMPAT,
"Extending %s shouldn't be used with stagedBuilder style attribute, they are incompartible:"
+ " Staged builder generate series of staged interfaces, but extending builder actually"
+ " extends implementation and do not provide type safety for setting first attribute,"
+ " as well as stagedBuilder forces generated builder interfaces to leak in code using the builder"
+ " and hence defeating the purpose of using extending builder.",
t.getSimpleName());
}
}
}

public final class InnerModifiableDefinition extends InnerBaseClassDefinition {
public InnerModifiableDefinition() {
super(names().namings.typeInnerModifiable);
}

protected boolean isExtending(TypeElement t) {
return false;
}

protected void lateValidateSuper(TypeElement t) {
super.lateValidateSuper(t);

if (t.getKind() == ElementKind.CLASS) {
String superclassString = SourceExtraction.getSuperclassString(t);
String rawSuperclass = SourceTypes.extract(superclassString).getKey();
// We need to extend the base class
if (!rawSuperclass.equals(typeAbstract().toString())) {
protoclass()
.report()
.withElement(t)
.error("%s needs to extend the base class",
t.getSimpleName());
}
}
}
}

public abstract class InnerBaseClassDefinition {
public final boolean isAccessibleFields;
public final boolean isPresent;
public final boolean isExtending;
Expand All @@ -693,42 +759,45 @@ public final class InnerBuilderDefinition {
public final Visibility visibility;
public final @Nullable String simpleName;
public final @Nullable Generics generics;
public final Naming naming;

InnerBaseClassDefinition(Naming naming) {
this.naming = naming;

InnerBuilderDefinition() {
@Nullable TypeElement builderElement = findBuilderElement();
@Nullable TypeElement baseElement = findBaseClassElement();
// The following series of checks designed
// to not validate inner builder if it's disabled,
// but at the same time we need such validation
// if we are using "extending" builder which is still allowed
// on demand even if builder feature is disabled
boolean extending = false;

if (builderElement != null) {
extending = isExtending(builderElement);
if (baseElement != null) {
extending = isExtending(baseElement);
}

if (builderElement != null && !protoclass().features().builder() && !extending) {
builderElement = null;
if (baseElement != null && !protoclass().features().builder() && !extending) {
baseElement = null;
}

if (builderElement != null && !isValidInnerBuilder(builderElement)) {
builderElement = null;
if (baseElement != null && !isValidInnerBaseClass(baseElement)) {
baseElement = null;
}

if (builderElement != null) {
this.isAccessibleFields = AccessibleFieldsMirror.find(builderElement).isPresent();
if (baseElement != null) {
this.isAccessibleFields = AccessibleFieldsMirror.find(baseElement).isPresent();
this.isPresent = true;
this.isInterface = builderElement.getKind() == ElementKind.INTERFACE;
this.isInterface = baseElement.getKind() == ElementKind.INTERFACE;
this.isExtending = extending;
this.isSuper = !extending;
this.simpleName = builderElement.getSimpleName().toString();
this.visibility = Visibility.of(builderElement);
this.generics = new Generics(protoclass(), builderElement);
this.simpleName = baseElement.getSimpleName().toString();
this.visibility = Visibility.of(baseElement);
this.generics = new Generics(protoclass(), baseElement);
if (isExtending) {
lateValidateExtending(builderElement);
lateValidateExtending(baseElement);
}
if (isSuper) {
lateValidateSuper(builderElement);
lateValidateSuper(baseElement);
}
} else {
this.isAccessibleFields = false;
Expand All @@ -742,7 +811,7 @@ public final class InnerBuilderDefinition {
}
}

private void lateValidateSuper(TypeElement t) {
protected void lateValidateSuper(TypeElement t) {
List<String> undeclaredParams = Lists.newArrayList();
for (String v : this.generics.vars()) {
if (!generics().hasParameter(v)) {
Expand All @@ -762,7 +831,7 @@ private void lateValidateSuper(TypeElement t) {
}
}

private void lateValidateExtending(TypeElement t) {
protected void lateValidateExtending(TypeElement t) {
if (t.getModifiers().contains(Modifier.ABSTRACT)) {
protoclass()
.report()
Expand All @@ -779,34 +848,12 @@ private void lateValidateExtending(TypeElement t) {
t.getSimpleName(),
generics().def());
}

if (protoclass().styles().style().stagedBuilder()) {
protoclass()
.report()
.withElement(t)
.warning(About.INCOMPAT,
"Extending %s shouldn't be used with stagedBuilder style attribute, they are incompartible:"
+ " Staged builder generate series of staged interfaces, but extending builder actually"
+ " extends implementation and do not provide type safety for setting first attribute,"
+ " as well as stagedBuilder forces generated builder interfaces to leak in code using the builder"
+ " and hence defeating the purpose of using extending builder.",
t.getSimpleName());
}
}

private boolean isExtending(TypeElement element) {
if (element.getKind() == ElementKind.CLASS) {
String superclassString = SourceExtraction.getSuperclassString(element);
String rawSuperclass = SourceTypes.extract(superclassString).getKey();
// If we are extending yet to be generated builder, we detect it by having the same name
// as relative name of builder type
return rawSuperclass.endsWith(typeImplementationBuilder().relativeRaw());
}
return false;
}
protected abstract boolean isExtending(TypeElement t);

@Nullable
private TypeElement findBuilderElement() {
private TypeElement findBaseClassElement() {
Protoclass protoclass = protoclass();
if (!protoclass.kind().isValue()) {
return null;
Expand All @@ -815,17 +862,17 @@ private TypeElement findBuilderElement() {
ElementKind kind = t.getKind();
if (kind.isClass() || kind.isInterface()) {
String simpleName = t.getSimpleName().toString();
Naming typeInnerBuilderNaming = names().namings.typeInnerBuilder;
Naming typeInnerClassNaming = naming;

if (!typeInnerBuilderNaming.detect(simpleName).isEmpty()) {
if (!typeInnerClassNaming.detect(simpleName).isEmpty()) {
return (TypeElement) t;
}
}
}
return null;
}

private boolean isValidInnerBuilder(Element t) {
private boolean isValidInnerBaseClass(Element t) {
ElementKind kind = t.getKind();
if (kind != ElementKind.CLASS
&& kind != ElementKind.INTERFACE) {
Expand Down
Expand Up @@ -154,6 +154,10 @@ public Class<? extends Annotation> annotationType() {
@Override
public abstract String typeModifiable();

@Value.Parameter
@Override
public abstract String typeInnerModifiable();

@Value.Parameter
@Override
public abstract String typeWith();
Expand Down Expand Up @@ -419,6 +423,7 @@ static StyleInfo infoFrom(StyleMirror input) {
input.typeImmutableEnclosing(),
input.typeImmutableNested(),
input.typeModifiable(),
input.typeInnerModifiable(),
input.typeWith(),
input.packageGenerated(),
ToImmutableInfo.FUNCTION.apply(input.defaults()),
Expand Down
Expand Up @@ -108,6 +108,7 @@ class Scheme {
Naming create = Naming.from(style.create());
Naming toImmutable = Naming.from(style.toImmutable());
Naming typeModifiable = Naming.from(style.typeModifiable());
Naming typeInnerModifiable = Naming.from(style.typeInnerModifiable());

Naming[] attributeBuilder = Naming.fromAll(style.attributeBuilder());
Naming getBuilder = Naming.from(style.getBuilder());
Expand Down
Expand Up @@ -149,6 +149,8 @@ private ValueMirrors() {}

String typeModifiable() default "Modifiable*";

String typeInnerModifiable() default "Modifiable";

String typeWith() default "With*";

String packageGenerated() default "*";
Expand Down
Expand Up @@ -64,6 +64,7 @@
import org.immutables.value.processor.meta.AnnotationInjections.InjectAnnotation.Where;
import org.immutables.value.processor.meta.Constitution.AppliedNameForms;
import org.immutables.value.processor.meta.Constitution.InnerBuilderDefinition;
import org.immutables.value.processor.meta.Constitution.InnerModifiableDefinition;
import org.immutables.value.processor.meta.Constitution.NameForms;
import org.immutables.value.processor.meta.Proto.DeclaringType;
import org.immutables.value.processor.meta.Proto.Environment;
Expand Down Expand Up @@ -653,6 +654,10 @@ public InnerBuilderDefinition getInnerBuilder() {
return constitution.innerBuilder();
}

public InnerModifiableDefinition getInnerModifiable() {
return constitution.innerModifiable();
}

public String getDocumentName() {
Optional<RepositoryMirror> repositoryAnnotation = RepositoryMirror.find(element);
if (repositoryAnnotation.isPresent()) {
Expand Down

0 comments on commit 67f8083

Please sign in to comment.