Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
= Changelog

== v2026.X.X

=== Dependency update

=== New features


=== Improvements


=== Bug fixes

- https://github.com/ObeoNetwork/pepper/issues/68[#68] Prevent cyclic dependencies creation
Prevent dependency creation between a task and one of its child and a task depending on one of the target task's parent


== v2026.6.1

=== Dependency update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,15 +523,39 @@ private Project getProject(EObject eObject) {
}

private IfDescription createComputeDynamicallyWidget() {
CheckboxDescription computeDynamically = this.propertiesWidgetCreationService.createCheckbox("abstractTask.computeDynamically", abstractTaskAdapter.getString("_UI_AbstractTask_computeStartEndDynamically_feature"),
task -> ((AbstractTask) task).isComputeStartEndDynamically(),
(object, newValue) -> {
Task task = (Task) object;
task.setComputeStartEndDynamically(newValue);
service.followMoveDependency(task);
},
PepperPackage.Literals.ABSTRACT_TASK__COMPUTE_START_END_DYNAMICALLY,
Optional.empty());
Function<VariableManager, Boolean> valueProvider = variableManager -> variableManager.get(VariableManager.SELF, AbstractTask.class)
.map(AbstractTask::isComputeStartEndDynamically)
.orElse(false);

Function<VariableManager, Boolean> isReadOnlyProvider = variableManager -> variableManager.get(VariableManager.SELF, AbstractTask.class)
.map(task -> !((Task) task).getDependencies().isEmpty())
.orElse(true);

BiFunction<VariableManager, Boolean, IStatus> newValueHandler = (variableManager, newValue) -> {
var taskOpt = variableManager.get(VariableManager.SELF, AbstractTask.class);
if (taskOpt.isPresent()) {
Task task = (Task) taskOpt.get();
task.setComputeStartEndDynamically(newValue != null && newValue);
service.followMoveDependency(task);
return new Success();
} else {
return new Failure("");
}
};

String id = "abstractTask.computeDynamically";
CheckboxDescription computeDynamically = CheckboxDescription.newCheckboxDescription(id)
.idProvider(variableManager -> id)
.targetObjectIdProvider(this.propertiesConfigurerService.getSemanticTargetIdProvider())
.labelProvider(variableManager -> abstractTaskAdapter.getString("_UI_AbstractTask_computeStartEndDynamically_feature"))
.iconURLProvider(variableManager -> List.of())
.isReadOnlyProvider(isReadOnlyProvider)
.valueProvider(valueProvider)
.newValueHandler(newValueHandler)
.diagnosticsProvider(this.propertiesConfigurerService.getDiagnosticsProvider(PepperPackage.Literals.ABSTRACT_TASK__COMPUTE_START_END_DYNAMICALLY))
.kindProvider(this.propertiesConfigurerService.getKindProvider())
.messageProvider(this.propertiesConfigurerService.getMessageProvider())
.build();

return IfDescription.newIfDescription("if.abstractTask.computeDynamically")
.targetObjectIdProvider(variableManager -> variableManager.get(VariableManager.SELF, Object.class).map(this.identityService::getId).orElse(null))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,39 @@ public void createDependencyLink(EObject target, EObject source, org.eclipse.sir
if (target instanceof DependencyRelatedObject targetObject) {
//Ensure no dependency already exists between source and target to prevent duplicates or cycles
if (!this.isDuplicateOrCycle(sourceObject, targetObject)) {
targetObject.getDependencies().add(dependencyLink);
this.followMoveDependency(sourceObject);
//Ensure the target task is not computed dynamically
if (targetObject instanceof Task targetTask && targetTask.isComputeStartEndDynamically()) {
this.feedbackMessageService.addFeedbackMessage(new Message("Creating a dependency targeting a dynamically computed task is not possible.", MessageLevel.ERROR));
} else {
targetObject.getDependencies().add(dependencyLink);
this.followMoveDependency(sourceObject);
}
}
}
}
}

private static boolean isCycle(DependencyRelatedObject sourceObject, DependencyRelatedObject targetObject) {
/**
* Checks if the source task already depends on one of the target task's ancestor tasks.
*
* @param sourceObject the {@link DependencyRelatedObject} source
* @param parent the parent {@link Task} of the target
* @return {@code true} if the source already depends on the specified parent or one of its ancestors; {@code false} otherwise
*/
private boolean isParentCycle(DependencyRelatedObject sourceObject, Task parent) {
for (DependencyLink dep : sourceObject.getDependencies()) {
if (dep.getSource().equals(parent)) {
return true;
}
}
boolean isParentCycle = false;
if (parent.eContainer() instanceof Task grandParent) {
isParentCycle = isParentCycle(sourceObject, grandParent);
}
return isParentCycle;
}

private boolean isCycle(DependencyRelatedObject sourceObject, DependencyRelatedObject targetObject) {
boolean isCycle = false;
for (DependencyLink dep : sourceObject.getDependencies()) {
if (dep.getSource().equals(targetObject)) {
Expand All @@ -315,9 +340,25 @@ private static boolean isCycle(DependencyRelatedObject sourceObject, DependencyR
*/
private boolean isDuplicateOrCycle(DependencyRelatedObject sourceObject, DependencyRelatedObject targetObject) {
//to prevent cycles
boolean isParentChildDependency = sourceObject.equals(targetObject.eContainer()) || targetObject.equals(sourceObject.eContainer());
boolean isParentCycle = false;
if (targetObject.eContainer() instanceof Task parent) {
isParentCycle = isParentCycle(sourceObject, parent);
}

boolean isCycle = isCycle(sourceObject, targetObject);

if (isParentChildDependency) {
this.feedbackMessageService.addFeedbackMessage(new Message("Creating a dependency between a parent task and one of its children is not possible.", MessageLevel.ERROR));
}
if (isParentCycle) {
this.feedbackMessageService.addFeedbackMessage(new Message("Creating a dependency when the source task already depends on one of the target task's parent tasks is not possible", MessageLevel.ERROR));
}
if (isCycle) {
this.feedbackMessageService.addFeedbackMessage(new Message("Creating a cyclic dependency is not possible.", MessageLevel.ERROR));
}

if (isCycle || isParentCycle || isParentChildDependency) {
return true;
} else {
//to prevent duplicates
Expand Down
Loading