Skip to content

Commit

Permalink
[1879] Update our ArchUnit tests to allow builders in records
Browse files Browse the repository at this point in the history
Bug: #1879
Signed-off-by: Stéphane Bégaudeau <stephane.begaudeau@obeo.fr>
  • Loading branch information
sbegaudeau committed Mar 22, 2023
1 parent f04a0b2 commit a4e24b9
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 87 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ This has no visible effect at the moment, but will allow better documentation, v
- [studio] Add a project template for the Papaya domains and view
- https://github.com/eclipse-sirius/sirius-components/issues/1667[#1667] [diagram] Make the contextual palette self contained
- https://github.com/eclipse-sirius/sirius-components/issues/1858[#1858] [project] Improve project template cards layout
- https://github.com/eclipse-sirius/sirius-components/issues/1879[#1879] [core] Add an annotation `@Builder` to identify builders and simplify some use cases involving the builder pattern.
It is now possible to use builders in records and it is a first step toward the deprecation of the `@Immutable` annotation in favor of more records.


== v2023.3.0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Interface used to indicate that a class is used as a builder.
*
* @author sbegaudeau
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Builder {
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ public class EditingContextEventProcessor implements IEditingContextEventProcess
private final Disposable changeDescriptionDisposable;

public EditingContextEventProcessor(EditingContextEventProcessorParameters parameters) {
this.messageService = parameters.getMessageService();
this.editingContext = parameters.getEditingContext();
this.editingContextPersistenceService = parameters.getEditingContextPersistenceService();
this.applicationEventPublisher = parameters.getApplicationEventPublisher();
this.editingContextEventHandlers = parameters.getEditingContextEventHandlers();
this.representationEventProcessorComposedFactory = parameters.getRepresentationEventProcessorComposedFactory();
this.danglingRepresentationDeletionService = parameters.getDanglingRepresentationDeletionService();
this.executorService = parameters.getExecutorServiceProvider().getExecutorService(this.editingContext);
this.messageService = parameters.messageService();
this.editingContext = parameters.editingContext();
this.editingContextPersistenceService = parameters.editingContextPersistenceService();
this.applicationEventPublisher = parameters.applicationEventPublisher();
this.editingContextEventHandlers = parameters.editingContextEventHandlers();
this.representationEventProcessorComposedFactory = parameters.representationEventProcessorComposedFactory();
this.danglingRepresentationDeletionService = parameters.danglingRepresentationDeletionService();
this.executorService = parameters.executorServiceProvider().getExecutorService(this.editingContext);
this.changeDescriptionDisposable = this.setupChangeDescriptionSinkConsumer();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.util.List;
import java.util.Objects;

import org.eclipse.sirius.components.annotations.Immutable;
import org.eclipse.sirius.components.annotations.Builder;
import org.eclipse.sirius.components.collaborative.api.IDanglingRepresentationDeletionService;
import org.eclipse.sirius.components.collaborative.api.IEditingContextEventHandler;
import org.eclipse.sirius.components.collaborative.api.IRepresentationEventProcessorComposedFactory;
Expand All @@ -30,71 +30,39 @@
*
* @author sbegaudeau
*/
@Immutable
public final class EditingContextEventProcessorParameters {
private ICollaborativeMessageService messageService;

private IEditingContext editingContext;

private IEditingContextPersistenceService editingContextPersistenceService;

private ApplicationEventPublisher applicationEventPublisher;

private List<IEditingContextEventHandler> editingContextEventHandlers;

private IRepresentationEventProcessorComposedFactory representationEventProcessorComposedFactory;

private IDanglingRepresentationDeletionService danglingRepresentationDeletionService;

private IEditingContextEventProcessorExecutorServiceProvider executorServiceProvider;

private EditingContextEventProcessorParameters() {
// Prevent instantiation
}

public ICollaborativeMessageService getMessageService() {
return this.messageService;
}

public IEditingContext getEditingContext() {
return this.editingContext;
}

public IEditingContextPersistenceService getEditingContextPersistenceService() {
return this.editingContextPersistenceService;
}

public ApplicationEventPublisher getApplicationEventPublisher() {
return this.applicationEventPublisher;
}

public List<IEditingContextEventHandler> getEditingContextEventHandlers() {
return this.editingContextEventHandlers;
}

public IRepresentationEventProcessorComposedFactory getRepresentationEventProcessorComposedFactory() {
return this.representationEventProcessorComposedFactory;
}

public IDanglingRepresentationDeletionService getDanglingRepresentationDeletionService() {
return this.danglingRepresentationDeletionService;
}

public IEditingContextEventProcessorExecutorServiceProvider getExecutorServiceProvider() {
return this.executorServiceProvider;
public record EditingContextEventProcessorParameters(
ICollaborativeMessageService messageService,
IEditingContext editingContext,
IEditingContextPersistenceService editingContextPersistenceService,
ApplicationEventPublisher applicationEventPublisher,
List<IEditingContextEventHandler> editingContextEventHandlers,
IRepresentationEventProcessorComposedFactory representationEventProcessorComposedFactory,
IDanglingRepresentationDeletionService danglingRepresentationDeletionService,
IEditingContextEventProcessorExecutorServiceProvider executorServiceProvider
) {
public EditingContextEventProcessorParameters {
Objects.requireNonNull(messageService);
Objects.requireNonNull(editingContext);
Objects.requireNonNull(editingContextPersistenceService);
Objects.requireNonNull(applicationEventPublisher);
Objects.requireNonNull(editingContextEventHandlers);
Objects.requireNonNull(representationEventProcessorComposedFactory);
Objects.requireNonNull(danglingRepresentationDeletionService);
Objects.requireNonNull(executorServiceProvider);
}

public static Builder newEditingContextEventProcessorParameters() {
return new Builder();
public static EditingContextEventProcessorParametersBuilder newEditingContextEventProcessorParameters() {
return new EditingContextEventProcessorParametersBuilder();
}

/**
* The builder used to create the parameters.
*
* @author sbegaudeau
*/
@Builder
@SuppressWarnings("checkstyle:HiddenField")
public static final class Builder {
public static final class EditingContextEventProcessorParametersBuilder {
private ICollaborativeMessageService messageService;

private IEditingContext editingContext;
Expand All @@ -111,61 +79,61 @@ public static final class Builder {

private IEditingContextEventProcessorExecutorServiceProvider executorServiceProvider;

private Builder() {
private EditingContextEventProcessorParametersBuilder() {
// Prevent instantiation
}

public Builder messageService(ICollaborativeMessageService messageService) {
public EditingContextEventProcessorParametersBuilder messageService(ICollaborativeMessageService messageService) {
this.messageService = Objects.requireNonNull(messageService);
return this;
}

public Builder editingContext(IEditingContext editingContext) {
public EditingContextEventProcessorParametersBuilder editingContext(IEditingContext editingContext) {
this.editingContext = Objects.requireNonNull(editingContext);
return this;
}

public Builder editingContextPersistenceService(IEditingContextPersistenceService editingContextPersistenceService) {
public EditingContextEventProcessorParametersBuilder editingContextPersistenceService(IEditingContextPersistenceService editingContextPersistenceService) {
this.editingContextPersistenceService = Objects.requireNonNull(editingContextPersistenceService);
return this;
}

public Builder applicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
public EditingContextEventProcessorParametersBuilder applicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = Objects.requireNonNull(applicationEventPublisher);
return this;
}

public Builder editingContextEventHandlers(List<IEditingContextEventHandler> editingContextEventHandlers) {
public EditingContextEventProcessorParametersBuilder editingContextEventHandlers(List<IEditingContextEventHandler> editingContextEventHandlers) {
this.editingContextEventHandlers = Objects.requireNonNull(editingContextEventHandlers);
return this;
}

public Builder representationEventProcessorComposedFactory(IRepresentationEventProcessorComposedFactory representationEventProcessorComposedFactory) {
public EditingContextEventProcessorParametersBuilder representationEventProcessorComposedFactory(IRepresentationEventProcessorComposedFactory representationEventProcessorComposedFactory) {
this.representationEventProcessorComposedFactory = Objects.requireNonNull(representationEventProcessorComposedFactory);
return this;
}

public Builder danglingRepresentationDeletionService(IDanglingRepresentationDeletionService danglingRepresentationDeletionService) {
public EditingContextEventProcessorParametersBuilder danglingRepresentationDeletionService(IDanglingRepresentationDeletionService danglingRepresentationDeletionService) {
this.danglingRepresentationDeletionService = Objects.requireNonNull(danglingRepresentationDeletionService);
return this;
}

public Builder executorServiceProvider(IEditingContextEventProcessorExecutorServiceProvider executorServiceProvider) {
public EditingContextEventProcessorParametersBuilder executorServiceProvider(IEditingContextEventProcessorExecutorServiceProvider executorServiceProvider) {
this.executorServiceProvider = Objects.requireNonNull(executorServiceProvider);
return this;
}

public EditingContextEventProcessorParameters build() {
EditingContextEventProcessorParameters parameters = new EditingContextEventProcessorParameters();
parameters.messageService = Objects.requireNonNull(this.messageService);
parameters.editingContext = Objects.requireNonNull(this.editingContext);
parameters.editingContextPersistenceService = Objects.requireNonNull(this.editingContextPersistenceService);
parameters.applicationEventPublisher = Objects.requireNonNull(this.applicationEventPublisher);
parameters.editingContextEventHandlers = Objects.requireNonNull(this.editingContextEventHandlers);
parameters.representationEventProcessorComposedFactory = Objects.requireNonNull(this.representationEventProcessorComposedFactory);
parameters.danglingRepresentationDeletionService = Objects.requireNonNull(this.danglingRepresentationDeletionService);
parameters.executorServiceProvider = Objects.requireNonNull(this.executorServiceProvider);
return parameters;
return new EditingContextEventProcessorParameters(
this.messageService,
this.editingContext,
this.editingContextPersistenceService,
this.applicationEventPublisher,
this.editingContextEventHandlers,
this.representationEventProcessorComposedFactory,
this.danglingRepresentationDeletionService,
this.executorServiceProvider
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
import com.tngtech.archunit.lang.SimpleConditionEvent;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.library.GeneralCodingRules;

import java.lang.annotation.Target;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.Set;

import org.eclipse.sirius.components.annotations.Builder;
import org.eclipse.sirius.components.annotations.Immutable;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -376,16 +376,19 @@ public void check(JavaClass javaClass, ConditionEvents events) {
* </ul>
*
* With the introduction of this test, it appears that apart from some utility constructors like in the @Immutable
* classes, we do not have a single static method with a real behavior. Thus nothing of value will be lost.
* classes, we do not have a single static method with a real behavior. Thus, nothing of value will be lost.
*
* In this test, we will ignore the following use cases:
* In this test, we will ensure that static methods are either used to return a builder or they act as a builder
* directly and are thus used to return a new instance. We will prevent the introduction of any static method in
* the code apart from the following use cases:
*
* <ul>
* <li>Enum since Java enum are considered as extending java.lang.Enum which comes with static methods</li>
* <li>Java 8+ lambdas which are compiled to hidden static methods (to make it short)</li>
* <li>@Parameters methods used by JUnit test cases</li>
* <li>@Immutable classes which are using a static method to create the builder</li>
* <li>@Constructor methods which are used as alternate constructors</li>
* <li>Static methods defined on records returning the record on which they are defined</li>
* <li>Static methods returning a class annotated with @Builder</li>
* </ul>
*/
@Test
Expand All @@ -405,6 +408,8 @@ public void noMethodsShouldBeStatic() {
.areDeclaredInClassesThat(this.isNotTestCase())
.and(this.isNotLambda())
.and(this.isNotSwitchTable())
.and(this.isNotRecordStaticBuilder())
.and(this.isNotReturningAnnotatedBuilder())
.should()
.beStatic();
// @formatter:on
Expand Down Expand Up @@ -449,4 +454,32 @@ public boolean apply(JavaMethod javaMethod) {
}
};
}

/**
* Used to detect that the static method is not defined in a record and does not return the same record.
*
* @return A predicate used to ignore some static methods
*/
private DescribedPredicate<JavaMethod> isNotRecordStaticBuilder() {
return new DescribedPredicate<>("is not an alternate builder in a record") {
@Override
public boolean apply(JavaMethod javaMethod) {
return !(javaMethod.getOwner().isRecord() && javaMethod.getRawReturnType().equals(javaMethod.getOwner()));
}
};
}

/**
* Used to detect that the static method is not returning a type annotated with @Builder.
*
* @return A predicate used to ignore some static methods
*/
private DescribedPredicate<JavaMethod> isNotReturningAnnotatedBuilder() {
return new DescribedPredicate<>("is not returning a type annotated with @Builder") {
@Override
public boolean apply(JavaMethod javaMethod) {
return !javaMethod.getRawReturnType().isAnnotatedWith(Builder.class);
}
};
}
}

0 comments on commit a4e24b9

Please sign in to comment.