strolch/model/src/main/java/li/strolch/model/activity/Activity.java

917 lines
26 KiB
Java

/*
* Copyright 2015 Martin Smock <martin.smock@bluewin.ch>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package li.strolch.model.activity;
import li.strolch.exception.StrolchElementNotFoundException;
import li.strolch.exception.StrolchException;
import li.strolch.exception.StrolchModelException;
import li.strolch.exception.StrolchPolicyException;
import li.strolch.model.*;
import li.strolch.model.Locator.LocatorBuilder;
import li.strolch.model.parameter.Parameter;
import li.strolch.model.policy.PolicyDef;
import li.strolch.model.policy.PolicyDefs;
import li.strolch.model.visitor.StrolchElementVisitor;
import li.strolch.model.xml.StrolchXmlHelper;
import li.strolch.utils.dbc.DBC;
import java.text.MessageFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
import static li.strolch.model.StrolchModelConstants.BAG_PARAMETERS;
import static li.strolch.model.StrolchModelConstants.BAG_RELATIONS;
import static li.strolch.model.StrolchModelConstants.PolicyConstants.BAG_OBJECTIVES;
import static li.strolch.utils.collections.CollectionsHelper.singletonCollector;
/**
* Parameterized object grouping a collection of {@link Activity} and {@link Action} objects defining the process to be
* scheduled
*
* @author Martin Smock <martin.smock@bluewin.ch>
*/
public class Activity extends AbstractStrolchRootElement
implements IActivityElement, StrolchRootElement, Comparable<Activity> {
protected Locator locator;
protected Version version;
protected Activity parent;
protected TimeOrdering timeOrdering;
protected Map<String, IActivityElement> elements;
protected PolicyDefs policyDefs;
/**
* Empty constructor - for marshalling only!
*/
public Activity() {
super();
}
/**
* Default constructor
*
* @param id the id
* @param name the name
* @param type the type
*/
public Activity(String id, String name, String type, TimeOrdering timeOrdering) {
super(id, name, type);
this.timeOrdering = timeOrdering;
}
@Override
public void setId(String id) {
this.locator = null;
super.setId(id);
}
@Override
public String getObjectType() {
return Tags.ACTIVITY;
}
public TimeOrdering getTimeOrdering() {
return this.timeOrdering;
}
public void setTimeOrdering(TimeOrdering timeOrdering) {
assertNotReadonly();
this.timeOrdering = timeOrdering;
}
@Override
public boolean hasVersion() {
return this.version != null;
}
@Override
public Version getVersion() {
return this.version;
}
@Override
public void setVersion(Version version) throws IllegalArgumentException, IllegalStateException {
if (!isRootElement())
throw new IllegalStateException("Can't set the version on non root of " + getLocator());
if (version != null && !getLocator().equals(version.getLocator())) {
String msg = "Illegal version as locator is not same: Element: {0} Version: {1}";
throw new IllegalArgumentException(MessageFormat.format(msg, getLocator(), version));
}
this.version = version;
}
private void initElements() {
if (this.elements == null) {
// use a LinkedHashMap since we will iterate elements in the order
// added and lookup elements by ID
this.elements = new LinkedHashMap<>();
}
}
@Override
public boolean isAction() {
return false;
}
@Override
public boolean isActivity() {
return true;
}
/**
* Returns true if this {@link Activity} contains any children i.e. any of {@link Action} or {@link Activity}
*
* @return true if this {@link Activity} contains any children i.e. any of {@link Action} or {@link Activity}
*/
public boolean hasElements() {
return this.elements != null && !this.elements.isEmpty();
}
/**
* Returns true if this {@link Activity} contains a child with the given id. The element instance type is ignored,
* i.e. {@link Action} or {@link Activity}
*
* @param id the id of the element to check for
*
* @return true if this {@link Activity} contains a child with the given id. The element instance type is ignored,
* i.e. {@link Action} or {@link Activity}
*/
public boolean hasElement(String id) {
return this.elements != null && this.elements.containsKey(id);
}
/**
* add an activity element to the {@code LinkedHashMap} of {@code IActivityElements}
*
* @param activityElement the element to add
*/
public void addElement(IActivityElement activityElement) {
assertCanAdd(activityElement);
activityElement.setParent(this);
this.elements.put(activityElement.getId(), activityElement);
}
/**
* add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} before the given element
*
* @param elementId the id of the element before which to add the other element
* @param elementToAdd the element to add
*/
public void addElementBefore(String elementId, IActivityElement elementToAdd) {
IActivityElement element = getElement(elementId);
addElementBefore(element, elementToAdd);
}
/**
* add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} before the given element
*
* @param element the element before which to add the other element
* @param elementToAdd the element to add
*/
public void addElementBefore(IActivityElement element, IActivityElement elementToAdd) {
assertCanAdd(elementToAdd);
Iterator<Entry<String, IActivityElement>> iterator = this.elements.entrySet().iterator();
LinkedHashMap<String, IActivityElement> elements = new LinkedHashMap<>();
boolean added = false;
while (iterator.hasNext()) {
Entry<String, IActivityElement> next = iterator.next();
if (!added && next.getValue().equals(element)) {
elements.put(elementToAdd.getId(), elementToAdd);
elements.put(next.getKey(), next.getValue());
added = true;
}
elements.put(next.getKey(), next.getValue());
}
if (!added)
throw new IllegalStateException("Element " + element.getId() + " was not found, couldn't add before!");
elementToAdd.setParent(this);
this.elements = elements;
}
/**
* add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} after the given element
*
* @param elementId the id of the element after which to add the other element
* @param elementToAdd the element to add
*/
public void addElementAfter(String elementId, IActivityElement elementToAdd) {
IActivityElement element = getElement(elementId);
addElementAfter(element, elementToAdd);
}
/**
* add an activity element to the {@code LinkedHashMap} of {@code IActivityElements} after the given element
*
* @param element the element after which to add the other element
* @param elementToAdd the element to add
*/
public void addElementAfter(IActivityElement element, IActivityElement elementToAdd) {
assertCanAdd(elementToAdd);
Iterator<Entry<String, IActivityElement>> iterator = this.elements.entrySet().iterator();
LinkedHashMap<String, IActivityElement> elements = new LinkedHashMap<>();
boolean added = false;
while (iterator.hasNext()) {
Entry<String, IActivityElement> next = iterator.next();
elements.put(next.getKey(), next.getValue());
if (!added && next.getValue().equals(element)) {
elements.put(elementToAdd.getId(), elementToAdd);
added = true;
}
}
if (!added)
throw new IllegalStateException("Element " + element.getId() + " was not found, couldn't add after!");
elementToAdd.setParent(this);
this.elements = elements;
}
private void assertCanAdd(IActivityElement elementToAdd) {
assertNotReadonly();
DBC.PRE.assertNotEquals("Can't add element to itself!", this, elementToAdd);
DBC.PRE.assertNull("Parent can't already be set!", elementToAdd.getParent());
// TODO make sure we can't create a circular dependency
initElements();
String id = elementToAdd.getId();
if (id == null)
throw new StrolchException("Cannot add IActivityElement without id.");
if (this.elements.containsKey(id))
throw new StrolchException(
"Activity " + getLocator() + " already contains an activity element with id = " + id);
}
/**
* Removes the element with the given id and returns it, if it exists
*
* @param id the id of the element to remove
*
* @return the removed element, or null if it does not exist
*/
@SuppressWarnings("unchecked")
public <T extends IActivityElement> T remove(String id) {
assertNotReadonly();
IActivityElement element = this.elements.remove(id);
if (element != null)
element.setParent(null);
return (T) element;
}
/**
* Returns the {@link Action} with the given ID which is a direct child of this {@link Activity}
*
* @param id the id of the {@link Action} to return
*
* @return the {@link Action} with the given ID
*/
public Action getAction(String id) {
return getElement(id);
}
/**
* Returns the {@link Activity} with the given ID which is a direct child of this {@link Activity}
*
* @param id the id of the {@link Activity} to return
*
* @return the {@link Activity} with the given ID
*/
public Activity getActivity(String id) {
return getElement(id);
}
/**
* get {@code IActivityElement} by id
*
* @param id the id of the {@code IActivityElement}
*
* @return IActivityElement
*/
public <T extends IActivityElement> T getElement(String id) {
if (this.elements == null)
throw new IllegalArgumentException("Element " + id + " does not exist on " + getLocator());
@SuppressWarnings("unchecked") T t = (T) this.elements.get(id);
if (t == null)
throw new IllegalArgumentException("Element " + id + " does not exist on " + getLocator());
return t;
}
public Optional<IActivityElement> getPreviousElement(IActivityElement element) {
if (!hasElements())
return Optional.empty();
IActivityElement previous = null;
Iterator<Entry<String, IActivityElement>> iter = elementIterator();
while (iter.hasNext()) {
IActivityElement elem = iter.next().getValue();
if (elem == element)
return Optional.ofNullable(previous);
else
previous = elem;
}
return Optional.empty();
}
public Optional<IActivityElement> getPreviousElementByType(IActivityElement element, String type) {
if (!hasElements())
return Optional.empty();
List<IActivityElement> reversed = new ArrayList<>(this.elements.values());
Collections.reverse(reversed);
boolean foundElem = false;
Iterator<IActivityElement> iter = reversed.iterator();
IActivityElement elem;
while (iter.hasNext()) {
elem = iter.next();
if (foundElem && elem.getType().equals(type))
return Optional.of(elem);
else if (elem == element) {
foundElem = true;
}
}
return Optional.empty();
}
public Optional<IActivityElement> getNextElement(IActivityElement element) {
if (!hasElements())
return Optional.empty();
Iterator<Entry<String, IActivityElement>> iter = elementIterator();
IActivityElement previous = iter.next().getValue();
while (iter.hasNext()) {
IActivityElement elem = iter.next().getValue();
if (previous == element)
return Optional.ofNullable(elem);
else
previous = elem;
}
return Optional.empty();
}
public Optional<IActivityElement> getNextElementByType(IActivityElement element, String type) {
if (!hasElements())
return Optional.empty();
Iterator<Entry<String, IActivityElement>> iter = elementIterator();
boolean foundElem = false;
IActivityElement elem;
while (iter.hasNext()) {
elem = iter.next().getValue();
if (foundElem && elem.getType().equals(type))
return Optional.of(elem);
else if (elem == element) {
foundElem = true;
}
}
return Optional.empty();
}
public List<IActivityElement> getElementsByType(String type) {
List<IActivityElement> elements = new ArrayList<>();
Iterator<Entry<String, IActivityElement>> iter = elementIterator();
while (iter.hasNext()) {
IActivityElement element = iter.next().getValue();
if (element.getType().equals(type))
elements.add(element);
}
return elements;
}
/**
* @return get the {@code LinkedHashMap} of {@code IActivityElements}
*/
public Map<String, IActivityElement> getElements() {
if (this.elements == null)
return Collections.emptyMap();
return this.elements;
}
/**
* @return the iterator for entries, which include the id as key and the {@link IActivityElement} as value
*/
public Iterator<Entry<String, IActivityElement>> elementIterator() {
if (this.elements == null)
return Collections.emptyIterator();
return this.elements.entrySet().iterator();
}
/**
* @return the stream for entries, which include the id as key and the {@link IActivityElement} as value
*/
public Stream<Entry<String, IActivityElement>> elementStream() {
if (this.elements == null)
return Stream.empty();
return this.elements.entrySet().stream();
}
public Stream<IActivityElement> streamElements() {
if (this.elements == null)
return Stream.empty();
return this.elements.values().stream();
}
public Stream<IActivityElement> streamElementsByType(String type) {
return streamElements().filter(e -> e.getType().equals(type));
}
public Stream<IActivityElement> streamElements(Predicate<IActivityElement> predicate) {
return streamElements().filter(predicate);
}
public Stream<Activity> streamActivities() {
return streamElements() //
.filter(IActivityElement::isActivity) //
.map(IActivityElement::asActivity);
}
public Stream<Activity> streamActivities(Predicate<Activity> predicate) {
return streamActivities().filter(predicate);
}
public Stream<Activity> streamActivitiesByType(String type) {
return streamActivities(a -> a.getType().equals(type));
}
public Stream<Action> streamActions() {
return streamElements() //
.filter(IActivityElement::isAction) //
.map(IActivityElement::asAction);
}
public Stream<Action> streamActions(Predicate<Action> predicate) {
return streamActions().filter(predicate);
}
public Stream<Action> streamActionsByType(String type) {
return streamActions(a -> a.getType().equals(type));
}
public Stream<Action> streamActionsDeep() {
return streamElements().flatMap(e -> {
if (e.isAction())
return Stream.of(e);
return e.asActivity().streamActionsDeep();
}).map(IActivityElement::asAction);
}
public Stream<Action> streamActionsDeep(Predicate<Action> predicate) {
return streamActionsDeep().filter(predicate);
}
public <T extends IActivityElement> T findElement(Predicate<IActivityElement> predicate,
Supplier<String> msgSupplier) {
@SuppressWarnings("unchecked") T t = (T) streamElements()
.filter(predicate)
.collect(singletonCollector(msgSupplier));
return t;
}
public List<IActivityElement> findElementsByType(String type) {
return findElements(e -> e.getType().equals(type));
}
public List<IActivityElement> findElements(Predicate<IActivityElement> predicate) {
return streamElements().filter(predicate).collect(toList());
}
public List<Action> findActions(Predicate<Action> predicate) {
return streamActions(predicate).collect(toList());
}
public List<Action> findActionsDeep(Predicate<Action> predicate) {
return streamActionsDeep().filter(predicate).collect(toList());
}
/**
* Returns all the actions as a flat list
*
* @return the list of actions
*/
public List<Action> getActionsAsFlatList() {
return streamActionsDeep().toList();
}
/**
* Returns all the actions in the entire hierarchy with the given state
*
* @param state the state of the action to return
*
* @return the list of actions with the given state
*/
public List<Action> getActionsWithState(State state) {
return streamActionsDeep(action -> action.getState() == state).toList();
}
/**
* Returns all the actions in the entire hierarchy with the given type
*
* @param type the type of action to return
*
* @return the list of actions with the given type
*/
public List<Action> getActionsByType(String type) {
return streamActionsDeep(action -> action.getType().equals(type)).toList();
}
public <T extends IActivityElement> T getElementByLocator(Locator locator) {
DBC.PRE.assertEquals("Locator is not for this activity!", getLocator(), locator.trim(3));
DBC.PRE.assertTrue("Locator must have at least 5 parts", locator.getSize() >= 4);
IActivityElement element = this;
for (int i = 3; i < locator.getSize(); i++) {
String next = locator.get(i);
if (!(element instanceof Activity)) {
String msg = "Invalid locator {0} with part {1} as not an Activity but deeper element specified";
throw new StrolchModelException(MessageFormat.format(msg, locator, next));
}
element = ((Activity) element).getElement(next);
if (element == null)
throw new StrolchElementNotFoundException(locator + " does not exist!");
}
@SuppressWarnings("unchecked") T t = (T) element;
return t;
}
@Override
public Activity asActivity() {
return this;
}
@Override
public Long getStart() {
long start = Long.MAX_VALUE;
if (this.elements == null)
return start;
Iterator<Entry<String, IActivityElement>> elementIterator = elementIterator();
while (elementIterator.hasNext()) {
IActivityElement element = elementIterator.next().getValue();
start = Math.min(start, element.getStart());
}
return start;
}
@Override
public Long getEnd() {
long end = 0L;
if (this.elements == null)
return end;
Iterator<Entry<String, IActivityElement>> elementIterator = elementIterator();
while (elementIterator.hasNext()) {
IActivityElement element = elementIterator.next().getValue();
end = Math.max(end, element.getEnd());
}
return end;
}
@Override
public State getState() {
if (this.elements == null || this.elements.isEmpty())
return State.CREATED;
return State.getState(this);
}
@Override
public PolicyDefs getPolicyDefs() {
if (this.policyDefs == null)
throw new StrolchPolicyException(getLocator() + " has no Policies defined!");
return this.policyDefs;
}
@Override
public PolicyDef getPolicyDef(Class<?> clazz) {
return getPolicyDefs().getPolicyDef(clazz);
}
@Override
public PolicyDef getPolicyDef(String type) {
return getPolicyDefs().getPolicyDef(type);
}
@Override
public PolicyDef getPolicyDef(Class<?> clazz, PolicyDef defaultDef) {
if (!hasPolicyDefs())
return defaultDef;
return getPolicyDefs().getPolicyDef(clazz, defaultDef);
}
@Override
public PolicyDef getPolicyDef(String type, PolicyDef defaultDef) {
if (!hasPolicyDefs())
return defaultDef;
return getPolicyDefs().getPolicyDef(type, defaultDef);
}
@Override
public boolean hasPolicyDefs() {
return this.policyDefs != null;
}
@Override
public boolean hasPolicyDef(String type) {
return this.policyDefs != null && this.policyDefs.hasPolicyDef(type);
}
@Override
public boolean hasPolicyDef(Class<?> clazz) {
return this.policyDefs != null && this.policyDefs.hasPolicyDef(clazz);
}
@Override
public void setPolicyDefs(PolicyDefs policyDefs) {
assertNotReadonly();
this.policyDefs = policyDefs;
this.policyDefs.setParent(this);
}
@Override
public void addOrUpdate(PolicyDef policyDef) {
assertNotReadonly();
DBC.PRE.assertNotNull("policyDef", policyDef);
if (this.policyDefs == null) {
this.policyDefs = new PolicyDefs();
this.policyDefs.setParent(this);
}
this.policyDefs.addOrUpdate(policyDef);
}
@Override
public Locator getLocator() {
if (this.locator == null) {
LocatorBuilder lb = new LocatorBuilder();
fillLocator(lb);
this.locator = lb.build();
}
return this.locator;
}
@Override
protected void fillLocator(LocatorBuilder locatorBuilder) {
if (this.parent != null)
this.parent.fillLocator(locatorBuilder);
else
locatorBuilder.append(Tags.ACTIVITY).append(getType());
locatorBuilder.append(getId());
}
@Override
public Activity getParent() {
return this.parent;
}
@Override
public Activity getRootElement() {
return (this.parent == null) ? this : this.parent.getRootElement();
}
@Override
public boolean isRootElement() {
return (this.parent == null);
}
@Override
public Activity getClone() {
return getClone(false);
}
@Override
public Activity getClone(boolean withVersion) {
Activity clone = new Activity();
super.fillClone(clone);
clone.timeOrdering = this.timeOrdering;
clone.locator = this.locator;
if (this.elements != null) {
for (IActivityElement element : this.elements.values()) {
clone.addElement(element.getClone());
}
}
if (this.policyDefs != null)
clone.policyDefs = this.policyDefs.getClone();
if (withVersion)
clone.version = this.version;
return clone;
}
@Override
public void setReadOnly() {
if (this.policyDefs != null)
this.policyDefs.setReadOnly();
elementStream().forEach(e -> e.getValue().setReadOnly());
super.setReadOnly();
}
@Override
public Activity ensureModifiable() {
if (!this.isRootElement())
throw new IllegalStateException("Only call this method on the root element!");
if (isReadOnly())
return getClone(true);
return this;
}
@Override
public Activity ensureReadOnly() {
if (isReadOnly())
return this;
return getClone(true).readOnly();
}
@Override
public Activity readOnly() {
setReadOnly();
return this;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
Activity a = (Activity) obj;
return this.parent == a.parent && this.type.equals(a.type) && this.id.equals(a.id);
}
@Override
public int hashCode() {
return Objects.hash(type, id);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(getLocator());
builder.append(", state=");
builder.append(getState());
builder.append(", start=");
builder.append(getStart());
builder.append(", end=");
builder.append(getEnd());
if (isRootElement() && this.version != null) {
builder.append(", version=");
builder.append(this.version.getVersion());
}
return builder.toString();
}
@Override
public int compareTo(Activity o) {
return getId().compareTo(o.getId());
}
@Override
public <T> T accept(StrolchElementVisitor<T> visitor) {
return visitor.visitActivity(this);
}
@Override
public <U, T extends Parameter<U>> T findObjectivesParam(String paramKey) {
return findParameter(BAG_OBJECTIVES, paramKey);
}
@Override
public <U, T extends Parameter<U>> T findObjectivesParam(String paramKey, boolean assertExists) {
return findParameter(BAG_OBJECTIVES, paramKey, assertExists);
}
@Override
public <U, T extends Parameter<U>> T findRelationParam(String paramKey) {
return findParameter(BAG_RELATIONS, paramKey);
}
@Override
public <U, T extends Parameter<U>> T findRelationParam(String paramKey, boolean assertExists) {
return findParameter(BAG_RELATIONS, paramKey, assertExists);
}
@Override
public <U, T extends Parameter<U>> T findParameter(String paramKey) {
return findParameter(BAG_PARAMETERS, paramKey);
}
@Override
public <U, T extends Parameter<U>> T findParameter(String paramKey, boolean assertExists) {
return findParameter(BAG_PARAMETERS, paramKey, assertExists);
}
public <U, T extends Parameter<U>> T findParameter(String bagKey, String paramKey) {
T parameter = getParameter(bagKey, paramKey);
if (parameter != null)
return parameter;
if (this.parent != null)
return this.parent.findParameter(bagKey, paramKey);
return null;
}
@Override
public <U, T extends Parameter<U>> T findParameter(String bagKey, String paramKey, boolean assertExists)
throws StrolchModelException {
T parameter = getParameter(bagKey, paramKey);
if (parameter != null)
return parameter;
parameter = this.parent == null ? null : this.parent.findParameter(bagKey, paramKey);
if (assertExists && parameter == null) {
String msg = "The Parameter {0} does not exist";
throw new StrolchModelException(MessageFormat.format(msg, getLocator().append(Tags.BAG, bagKey, paramKey)));
}
return parameter;
}
@Override
public PolicyDef findPolicy(Class<?> clazz, PolicyDef defaultDef) throws StrolchModelException {
return findPolicy(clazz.getSimpleName(), defaultDef);
}
@Override
public PolicyDef findPolicy(String className, PolicyDef defaultDef) throws StrolchModelException {
if (hasPolicyDef(className))
return getPolicyDef(className);
if (this.parent == null) {
if (defaultDef != null)
return defaultDef;
String msg = "The PolicyDef {0} does not exist";
throw new StrolchModelException(MessageFormat.format(msg, className));
}
return this.parent.findPolicy(className, defaultDef);
}
@Override
public void setParent(Activity activity) {
assertNotReadonly();
this.parent = activity;
this.locator = null;
}
/**
* Creates a {@link Locator} for activities of the given type and id
*
* @param type the type of activity
* @param id the id of the activity
*
* @return the locator
*/
public static Locator locatorFor(String type, String id) {
return Locator.valueOf(Tags.ACTIVITY, type, id);
}
/**
* Parses the given XML and returns the activity with the given ID
*
* @param xml the xml to parse
* @param id the id of the activity to return from the parsed elements
*
* @return the activity, or null if it does not exist
*/
public static Resource parse(String xml, String id) {
return StrolchXmlHelper.parseAndReturnResource(xml, id);
}
}