org.requirementsascode.AbstractActor Maven / Gradle / Ivy
Show all versions of requirementsascodecore Show documentation
package org.requirementsascode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.requirementsascode.exception.InfiniteRepetition;
import org.requirementsascode.exception.MoreThanOneStepCanReact;
/**
* An actor can be anything with a behavior.
* It can be the system/service you're developing,
* or a role that an external user plays.
*
* Actors can be senders and receivers of messages to
* other actors.
*
* Actors enable to distinguish user rights:
* only an actor that is connected to a particular step is allowed to
* cause a system reaction for that step.
*
* @author b_muth
*/
public abstract class AbstractActor {
private String name;
private ModelRunner modelRunner;
private Map> useCaseToStepMap;
/**
* Creates an actor with a name equal to the current class' simple name.
*
*/
public AbstractActor() {
initializeFields();
setName(getClass().getSimpleName());
}
/**
* Creates an actor with the specified name.
*
* @param name the name of the actor
*/
public AbstractActor(String name) {
initializeFields();
setName(name);
}
private void initializeFields() {
createEmptyUseCaseToStepMap();
createOwnedModelRunner();
}
private void createEmptyUseCaseToStepMap() {
this.useCaseToStepMap = new HashMap<>();
}
protected void createOwnedModelRunner() {
this.modelRunner = new ModelRunner();
this.modelRunner.setOwningActor(this);
}
/**
* Returns the name of the actor.
*
* @return the name
*/
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
/**
* Returns the use cases this actor is associated with,
* as an external user.
*
*
* The actor is associated to a use case if it is connected to at least one of
* its steps.
*
*
* Note: don't confuse this with the
* use cases that the actor owns as part of its behavior.
* @return the use cases the actor is associated with
*/
public Set getUseCases() {
Set useCases = useCaseToStepMap.keySet();
return Collections.unmodifiableSet(useCases);
}
/**
* Returns the steps this actor is connected with, for the specified use case.
*
* Note: don't confuse this with the
* steps that the actor owns as part of its behavior.
*
* @param useCase the use case to query for steps the actor is connected with
* @return the steps the actor is connected with
*/
public List getStepsOf(UseCase useCase) {
Objects.requireNonNull(useCase);
List steps = getModifiableStepsOf(useCase);
return Collections.unmodifiableList(steps);
}
private List getModifiableStepsOf(UseCase useCase) {
useCaseToStepMap.putIfAbsent(useCase, new ArrayList<>());
return useCaseToStepMap.get(useCase);
}
/**
* Runs the ModelRunner encapsulated by this actor.
* You only need to explicitly call this method if your model contains
* steps that have a condition, but no event/command defined.
*/
public void run() {
runBehavior();
}
private void runBehavior() {
Model actorBehavior = behavior();
if(actorBehavior != null) {
modelRunner.run(actorBehavior);
}
}
/**
* Call this method to provide a message (i.e. command or event object) to the
* actor.
*
*
* The actor will then check which steps can react. If a
* single step can react, the actor will call the message handler with it. If
* no step can react, the actor will either call the handler defined with
* {@link ModelRunner#handleUnhandledWith(Consumer)} on the actor's model runner,
* or if no such handler exists, consume the message silently.
*
* If more than one step can react, the actor will throw an exception.
*
* After that, the actor will trigger "autonomous system reactions"
* that don't have a message class.
*
* Note that if you provide a collection as the first and only argument, this
* will be flattened to the objects in the collection, and for each object
* {@link #reactTo(Object)} is called.
*
* @see AbstractActor#getModelRunner()
* @see ModelRunner#handleUnhandledWith(Consumer)
* @see ModelRunner#reactTo(Object)
*
* @param the type of message
* @param the return type that you as the user expects.
* @param message the message object
* @return the event that was published (latest) if the system reacted, or an empty Optional.
*
* @throws MoreThanOneStepCanReact when more than one step can react
* @throws InfiniteRepetition when a step has an always true condition, or
* there is an infinite loop.
* @throws ClassCastException when type of the returned instance isn't U
*/
public Optional reactTo(T message) {
if(!modelRunner.isRunning()) {
runBehavior();
}
Optional latestPublishedEvent = modelRunner.reactTo(message);
return latestPublishedEvent;
}
/**
* Same as {@link #reactTo(Object)}, but with the specified actor as the calling user's role.
*
* @param the type of message
* @param the return type that you as the user expects.
* @param message the message object
* @param callingActor the actor as which to call this actor.
* @return the event that was published (latest) if the system reacted, or an empty Optional.
*/
public Optional reactTo(Object message, AbstractActor callingActor) {
if(!modelRunner.isRunning()) {
runBehavior();
}
Optional latestPublishedEvent = modelRunner.as(callingActor).reactTo(message);
return latestPublishedEvent;
}
/**
* Override this method to provide the model for the actor's behavior.
*
* @return the behavior
*/
public abstract Model behavior();
/**
* Call this method from a subclass to customize the way the actor runs the behavior.
*
* @return the single model runner encapsulated by this actor
*/
public ModelRunner getModelRunner() {
return modelRunner;
}
void connectToStep(Step step) {
Objects.requireNonNull(step.getUseCase());
Objects.requireNonNull(step);
List steps = getModifiableStepsOf(step.getUseCase());
steps.add(step);
}
@Override
public String toString() {
return getName();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractActor other = (AbstractActor) obj;
if (getName() == null) {
if (other.getName() != null)
return false;
} else if (!getName().equals(other.getName()))
return false;
return true;
}
}