package ch.akuhn.values; import java.util.EventObject; import java.util.concurrent.atomic.AtomicReference; import ch.akuhn.util.Arrays; import ch.akuhn.util.ProgressMonitor; import ch.akuhn.values.TaskFactory.Callback; import ch.akuhn.values.TaskFactory.Task; @SuppressWarnings("unchecked") public abstract class TaskValue<V> extends AbstractValue<V> implements ValueChangedListener { private static void DEBUGF(String format, Object... args) { // System.out.printf(format, args); } private static String T() { return Thread.currentThread().getName(); } private final AtomicReference<State> fState; //private final BlockingQueue<ValueOrError<V>> fValue; private String name; private Value[] parts; private boolean requiresAllArguments; /** Tells this value to start tasks even if not all arguments are available. * This skips the waiting phase. Subclasses must deal with missing or failed * arguments! *<P> * To be called by constructor of subclasses. * */ protected void doesNotRequireAllArguments() { this.requiresAllArguments = false; } public TaskValue(String name, Value<?>... parts) { this.name = name; this.parts = parts; this.requiresAllArguments = true; //fValue = new ArrayBlockingQueue<ValueOrError<V>>(1); fState = new AtomicReference<State>(new Missing(null)); for (Value<?> each: parts) each.addDependent(this); } @Override public ImmutableValue<V> asImmutable() { this.requestValue(); return fState.get().innerAsImmutable(); } public V awaitValue() { throw new UnsupportedOperationException(); } @Override public Throwable getError() { return this.asImmutable().getError(); } @Override public V getValueOrFail() { return this.asImmutable().getValueOrFail(); } @Override public boolean isError() { return this.asImmutable().isError(); } @Override public void setValue(V value) { throw new UnsupportedOperationException(); } @Override public String toString() { return "Value("+name+")"; } @Override public V getValue() { return this.asImmutable().getValue(); } @Override public void valueChanged(EventObject event) { fState.get().innerValueChanged(event); } protected abstract V computeValue(ProgressMonitor monitor, Arguments arguments); private void requestValue() { fState.get().innerRequestValue(); } private class State { protected final Object value; protected final Throwable error; private State(Object value, Throwable error) { this.value = value; this.error = error; } @Override public String toString() { String basic = super.toString(); return String.format("Value(%s, %s)", name, basic.substring(basic.lastIndexOf('$') + 1)); } public ImmutableValue innerAsImmutable() { return new ImmutableValue(value, error); } public void innerRequestValue() { return; } public void innerValueChanged(EventObject event) { return; } protected boolean isNewArgumentValue(ImmutableValue[] arguments, Object source) { if (arguments == null) return true; int index = Arrays.indexOf(parts, source); if (index < 0) return false; // The cast to Value below is safe, only values have an index >= 0 return !Values.equal(arguments[index], ((Value) source).asImmutable()); } } private class Done extends State { protected ImmutableValue[] arguments; private Done(V value, Throwable error, ImmutableValue... arguments) { super(value, error); if (arguments.length != parts.length) throw new IllegalArgumentException(); this.arguments = arguments; } @Override public void innerValueChanged(EventObject event) { DEBUGF("%s\tupdate received %s from %s\n", T(), this, event.getSource()); if (isNewArgumentValue(arguments, event.getSource())) { State newState = new Missing(value); if (TaskValue.this.changeState(this, newState)) { changed(); } } } } private static Throwable MISSING = new Error() { @Override public synchronized Throwable fillInStackTrace() { return this; } }; private class Missing extends State { public Missing(Object value) { super(value, MISSING); } @Override public void innerRequestValue() { DEBUGF("%s\trequest %s\n", T(), this); Waiting newState = new Waiting(value, MISSING); if (TaskValue.this.changeState(this, newState)) { newState.start(); } } } private class Waiting extends State { private ImmutableValue[] arguments; private Waiting(Object value, Throwable error) { super (value, error); } protected void start() { DEBUGF("%s\tstart %s\n", T(), this); arguments = new ImmutableValue[parts.length]; for (int n = 0; n < parts.length; n++) arguments [n] = parts[n].asImmutable(); if (requiresAllArguments) for (ImmutableValue each: arguments) if (each.isError()) return; Working newState = new Working(value, error, arguments); if (TaskValue.this.changeState(this, newState)) { newState.start(); } } @Override public void innerValueChanged(EventObject event) { DEBUGF("%s\tupdate received %s from %s\n", T(), this, event.getSource()); if (!isNewArgumentValue(arguments, event.getSource())) return; this.start(); } @Override public void innerRequestValue() { // this.start(); TODO figure out how to broadcast requests while waiting // without ending up in an endless loop upon errors } } private static TaskFactory TASK_FACTORY = new TaskFactory(); public static void setTaskFactory(TaskFactory factory) { TASK_FACTORY = factory; } private class Working extends State implements Callback { private Task job; private ImmutableValue[] arguments; private Working(Object value, Throwable error, ImmutableValue[] arguments) { super(value, error); job = TASK_FACTORY.makeTask(); this.arguments = arguments; } @Override public String getName() { return name; } @Override public void innerValueChanged(EventObject event) { DEBUGF("%s\tupdate received %s from %s\n", T(), this, event.getSource()); if (isNewArgumentValue(arguments, event.getSource())) { Waiting newState = new Waiting(value, error); if (TaskValue.this.changeState(this, newState)) { newState.start(); } else { // let the current state handle this update TaskValue.this.fState.get().innerValueChanged(event); } this.stop(); } } @Override // Callback public Throwable run(ProgressMonitor monitor) { DEBUGF("%s\trun %s\n", T(), this); monitor.setName(name); State newState = runTryCatch(monitor); monitor.done(); if (TaskValue.this.changeState(this, newState)) { changed(); } return newState.error; } private State runTryCatch(ProgressMonitor monitor) { try { return new Done(computeValue(monitor, new Arguments(arguments)), null, arguments); } catch (Throwable ex) { return new Waiting(value, ex); } } protected void start() { DEBUGF("%s\tstart-running %s\n", T(), this); if (this != fState.get()) return; job.start(this); } private void stop() { DEBUGF("%s\tstop %s\n", T(), this); job.stop(); } @Override public void innerRequestValue() { return; } } private boolean changeState(State previousState, State newState) { DEBUGF("%s\tChanging state from %s to %s\n", T(), previousState, newState); if (fState.compareAndSet(previousState, newState)) { maybeDisposeValue((V) previousState.value, (V) newState.value); return true; } else { DEBUGF("%s\tFAIL, current state is %s\n", T(), fState); return false; } } protected void maybeDisposeValue(V value, V value2) { // react on changed values } }