de.tsl2.nano.util.Flow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.common Show documentation
Show all versions of tsl2.nano.common Show documentation
TSL2 Framework Commons (Collections, Actions/Excecution, Readers, Xml, Print, Mail, FuzzyFinder, Proxies, Network-Structure)
package de.tsl2.nano.util;
import java.io.File;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Scanner;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.NetUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
/**
* {@link Flow} as simplest workflow base using implementations of {@link ITask}.
* It is the maker. going recursively through all tasks until end. experimental implementation
* having only one class file with inner classes and a fat interface
*
* As {@link AFunctionalTask} is a base implementation of {@link ITask}, the {@link CTask}
* provides an extension to be used on functional implementation.
*
* Each task has a name (may be equal to the action definition representation), a condition
* for activation and the action itself.
*
* To persist a flow with its task tree, a gravito state diagram file will be created.
* This can be rendered by gravito inside a markdown file. So, a graphical representation is given.
*
* This base implementation of a workflow is used and extended inside the module 'tsl2.nano.specification'
* which provides conditions and actions as rules (through scripting, decision tables etc.) or specified actions.
* */
@SuppressWarnings({"unchecked", "rawtypes"})
public class Flow {
public static final String FILE_EXT = ".gra";
protected static Class extends ITask> DEFAULT_TASK_TYPE = BeanClass.load(System.getProperty("tsl2nano.flow.default.task.type", CTask.class.getName()));
protected static final String NAME_START = "START";
protected static final String NAME_END = "END";
protected static final String GRVT_LABEL_START = " [label=\"";
protected static final String GRVT_LABEL_END = "\"]";
String name;
ITask start;
List> listeners = new LinkedList<>();
public void setName(String name) {
this.name = name;
}
public void setTasks(ITask start) {
this.start = start;
}
public void setListeners(List> listeners) {
this.listeners = listeners;
}
public void persist() {
persist(FileUtil.userDirFile(name + FILE_EXT));
}
public void persist(File gravitoFile) {
StringBuilder buf = new StringBuilder();
buildString(start, buf);
Util.trY( () -> Files.write(Paths.get(gravitoFile.getPath()), buf.toString().getBytes()));
}
private StringBuilder buildString(ITask root, StringBuilder buf) {
buf.append(root.asString() + "\n");
root.next().forEach(t -> buildString(t, buf));
return buf;
}
public static Flow load(File gravitoFile) {
return load(gravitoFile, null);
}
public static Flow load(File gravitoFile, Class extends ITask> taskType) {
return load(new Flow(), gravitoFile, taskType);
}
public static F load(F flow, File gravitoFile, Class extends ITask> taskType) {
String name = StringUtil.substring(FileUtil.replaceToJavaSeparator(gravitoFile.getPath()), "/", ".", true);
String expression = FileUtil.getFileString(gravitoFile.getPath());
return fromString(flow, name, expression, taskType);
}
public static F fromString(F flow, String name, String expression, Class extends ITask> taskType) {
Scanner sc = Util.trY( () -> new Scanner(expression));
flow.name = name;
ITask task = null;
Map tasks = new HashMap<>();
// tasks.put(ITask.END.name(), ITask.END);
Deque strTasks = new LinkedList<>();
String line;
while (sc.hasNextLine()) {
if (!(line = sc.nextLine().trim()).isEmpty() && !line.startsWith("#"))
strTasks.add(line);
}
// create from end...
while (!strTasks.isEmpty()) {
// task = BeanClass.createInstance((Class extends ITask>)(taskType != null ? taskType : DEFAULT_TASK_TYPE));
task = taskType != null ? BeanClass.createInstance(taskType) : new CTask();
//TODO: thats nonsense - we throw the new instance away...
task = task.fromGravString(strTasks.pollLast(), tasks);
tasks.put(task.name(), task);
}
flow.setTasks(task);
return flow;
}
public String getName() {
return name;
}
public Deque run(Map context) {
LinkedList solved = new LinkedList<>();
flow(start, context, solved);
return solved;
}
private void flow(ITask current, Map context, List solved) {
if (current.canActivate(context)) {
Object result = current.activate(context);
context.put(current.name(), result);
listeners.forEach(l -> l.accept(current));
solved.add(current);
if (current.isStart() || current.status().equals(ITask.Status.OK)) {
for (ITask t : current.next()) {
flow(t, context, solved);
}
}
}
}
public void addListener(Consumer l) {
listeners.add(l);
}
public boolean isSuccessfull(Deque solved) {
return solved.getLast().isEnd();
}
public boolean isUnConditioned(Deque solved) {
return !solved.getLast().isEnd() && solved.getLast().status().equals(ITask.Status.OK);
}
public boolean isFailed(Deque solved) {
return solved.getLast().status().equals(ITask.Status.FAIL);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Flow))
return false;
Flow f = (Flow) obj;
return buildString(start, new StringBuilder()).toString().equals(f.buildString(f.start, new StringBuilder()).toString());
}
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("usage: Flow [key1=value1 [key2=value2]...]\n\ttries to load a same named property file");
return;
}
Flow flow = Flow.load(new File(args[0]), CTask.class);
Properties p = FileUtil.loadProperties(args[0] + ".properties");
MapUtil.fill(p, args, 1, "=");
flow.run(new HashMap<>((Map)Util.untyped(p)));
}
/** base definition to do a simple workflow */
public interface ITask {
enum Status {NEW, ASK, RUN, FAIL, OK}
String name();
default boolean canActivate(Map context) { return !context.containsValue(this); }
default Object activate(Map context) { return context.put(name(), this); }
default Status status() { return Status.NEW; }
default List next() { return Arrays.asList(END); }
void addNeighbours(ITask...tasks);
default boolean isStart() { return false; }
default boolean isEnd() { return false; }
default String asString() { // we can't override toString() in an interface or not-public class
StringBuilder buf = new StringBuilder();
next().forEach(n -> buf.append(name() + " (" + status() + ")" + " -> " + n.name() + n.gravCondition()));
return buf.toString();
}
default String gravCondition() {
return "";
}
void setCondition(String condition);
default ITask fromGravString(String line, Map tasks) {
String[] t = StringUtil.splitFix(line, false, " ", " -> ", GRVT_LABEL_START, GRVT_LABEL_END);
if (!line.contains("[label")) {
t[3] = t[4] = null;
}
ITask task2 = tasks.get(t[2]);
if (task2 == null)
task2 = t[2].equals(NAME_END) ? ITask.END : createTask(t[2], t[3], null);
else
task2.setCondition(t[3]);
ITask task1 /*= tasks.remove(t[0]);
if (task1 == null)
task1 */= t[0].equals(NAME_START) ? createStart(task2) : createTask(t[0], null, StringUtil.substring(t[1], "(", ")"));
if (!task1.name().equals(NAME_START))
task1.addNeighbours(task2);
return task1;
}
default ITask createTask(String name, String condition, String status) { throw new UnsupportedOperationException(); }
public static ITask createStart(final ITask...tasks) {
return new ITask() {
@Override public String name() { return NAME_START; }
@Override public boolean isStart() { return true; }
@Override public void addNeighbours(ITask...tasks) {}
@Override public void setCondition(String condition) {}
@Override
public List next() {
return Arrays.asList(tasks);
}
@Override
public Status status() {
return ITask.Status.OK;
}
@Override
public String toString() {
return asString();
}
};
}
static final ITask END = new ITask() {
String condition;
@Override public String name() { return NAME_END; }
@Override public List next() { return Arrays.asList(); }
@Override public boolean isEnd() { return true; }
@Override public void addNeighbours(ITask...tasks) {}
@Override public String gravCondition() { return condition != null ? GRVT_LABEL_START + condition + GRVT_LABEL_END : ""; }
@Override public void setCondition(String condition) { this.condition = condition;}
@Override public String toString() { return name();
}
};
}
public static abstract class ATask implements ITask {
protected String condition;
protected String expression;
private Predicate