diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b11d83893ae..cc6e7a0d05d 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -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 diff --git a/packages/core/backend/sirius-components-annotations/src/main/java/org/eclipse/sirius/components/annotations/Builder.java b/packages/core/backend/sirius-components-annotations/src/main/java/org/eclipse/sirius/components/annotations/Builder.java new file mode 100644 index 00000000000..da027ea1ba7 --- /dev/null +++ b/packages/core/backend/sirius-components-annotations/src/main/java/org/eclipse/sirius/components/annotations/Builder.java @@ -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 { +} diff --git a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java index ecd557228f0..f337a346dd9 100644 --- a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java +++ b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessor.java @@ -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(); } diff --git a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorParameters.java b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorParameters.java index 6ed9e3e1f47..26af5b542a1 100644 --- a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorParameters.java +++ b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorParameters.java @@ -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; @@ -30,62 +30,29 @@ * * @author sbegaudeau */ -@Immutable -public final class EditingContextEventProcessorParameters { - private ICollaborativeMessageService messageService; - - private IEditingContext editingContext; - - private IEditingContextPersistenceService editingContextPersistenceService; - - private ApplicationEventPublisher applicationEventPublisher; - - private List 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 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 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(); } /** @@ -93,8 +60,9 @@ public static Builder newEditingContextEventProcessorParameters() { * * @author sbegaudeau */ + @Builder @SuppressWarnings("checkstyle:HiddenField") - public static final class Builder { + public static final class EditingContextEventProcessorParametersBuilder { private ICollaborativeMessageService messageService; private IEditingContext editingContext; @@ -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 editingContextEventHandlers) { + public EditingContextEventProcessorParametersBuilder editingContextEventHandlers(List 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 + ); } } } diff --git a/packages/tests/backend/sirius-components-tests/src/main/java/org/eclipse/sirius/components/tests/architecture/AbstractCodingRulesTests.java b/packages/tests/backend/sirius-components-tests/src/main/java/org/eclipse/sirius/components/tests/architecture/AbstractCodingRulesTests.java index b332bbac5ed..d43de1be7cf 100644 --- a/packages/tests/backend/sirius-components-tests/src/main/java/org/eclipse/sirius/components/tests/architecture/AbstractCodingRulesTests.java +++ b/packages/tests/backend/sirius-components-tests/src/main/java/org/eclipse/sirius/components/tests/architecture/AbstractCodingRulesTests.java @@ -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; @@ -376,16 +376,19 @@ public void check(JavaClass javaClass, ConditionEvents events) { * * * 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: * *
    *
  • Enum since Java enum are considered as extending java.lang.Enum which comes with static methods
  • *
  • Java 8+ lambdas which are compiled to hidden static methods (to make it short)
  • *
  • @Parameters methods used by JUnit test cases
  • *
  • @Immutable classes which are using a static method to create the builder
  • - *
  • @Constructor methods which are used as alternate constructors
  • + *
  • Static methods defined on records returning the record on which they are defined
  • + *
  • Static methods returning a class annotated with @Builder
  • *
*/ @Test @@ -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 @@ -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 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 isNotReturningAnnotatedBuilder() { + return new DescribedPredicate<>("is not returning a type annotated with @Builder") { + @Override + public boolean apply(JavaMethod javaMethod) { + return !javaMethod.getRawReturnType().isAnnotatedWith(Builder.class); + } + }; + } }