package org.tessell.model.validation.rules;
import static org.tessell.model.properties.NewProperty.derivedProperty;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.tessell.model.properties.Property;
import org.tessell.model.properties.Upstream;
import org.tessell.model.properties.Upstream.Capture;
import org.tessell.model.properties.UpstreamState;
import org.tessell.model.validation.events.RuleTriggeredEvent;
import org.tessell.model.validation.events.RuleTriggeredHandler;
import org.tessell.model.validation.events.RuleUntriggeredEvent;
import org.tessell.model.validation.events.RuleUntriggeredHandler;
import org.tessell.model.values.LambdaValue;
import org.tessell.model.values.Value;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.SimplerEventBus;
/**
* A base class with most of the common {@link Rule} functionality implemented
*
* @param T
* the value of the property for this rule
* @param U
* the subclass for the {@link #getThis()} hack
*/
public abstract class AbstractRule<T> implements Rule<T> {
private static final Logger log = Logger.getLogger("org.tessell.model");
// handlers
protected final EventBus handlers = new SimplerEventBus();
// List of properties that must be true for this rule to run.
private final ArrayList<Value<Boolean>> onlyIf = new ArrayList<Value<Boolean>>();
// set by AbstractProperty.addRule
protected Property<T> property;
// The error message to show the user
protected String message;
// Whether this rule has been invalid and fired a RuleTriggeredEvent
private boolean triggered = false;
// only used if this is a derived value
private UpstreamState lastUpstream;
protected AbstractRule(String message) {
this.message = message;
}
protected abstract boolean isValid();
@Override
public final boolean validate() {
if (lastUpstream == null) {
lastUpstream = new UpstreamState(property, false);
}
Capture c = Upstream.start();
boolean v = doValidate();
lastUpstream.update(c.finish());
return v;
}
@Override
public void setProperty(Property<T> property) {
this.property = property;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public HandlerRegistration addRuleTriggeredHandler(final RuleTriggeredHandler handler) {
return handlers.addHandler(RuleTriggeredEvent.getType(), handler);
}
@Override
public HandlerRegistration addRuleUntriggeredHandler(final RuleUntriggeredHandler handler) {
return handlers.addHandler(RuleUntriggeredEvent.getType(), handler);
}
/** Only run this rule if {@code other} is true */
@Override
public Rule<T> onlyIf(final Value<Boolean> other) {
this.onlyIf.add(other);
if (property != null) {
property.reassess();
}
return this;
}
@Override
public Rule<T> onlyIf(final LambdaValue<Boolean> other) {
return onlyIf(derivedProperty(other));
}
public void triggerIfNeeded() {
if (!triggered && message != null) { // custom rules (PropertyGroup's) may not have explicit error messages
fireEvent(new RuleTriggeredEvent(this, message, new Boolean[] { false }));
triggered = true;
}
}
public void untriggerIfNeeded() {
if (triggered) {
fireEvent(new RuleUntriggeredEvent(this, message));
triggered = false;
}
}
@Override
public String toString() {
return message;
}
@Override
public boolean isImportant() {
return false;
}
private void fireEvent(final GwtEvent<?> event) {
log.log(Level.FINEST, this + " firing " + event);
handlers.fireEvent(event);
// after all of our handlers have seen event, delegate it up.
property.fireEvent(event);
}
// change this to push down too
private boolean onlyIfSaysToSkip() {
for (final Value<Boolean> only : onlyIf) {
if (only.get() != null && !only.get().booleanValue()) {
return true;
}
}
return false;
}
private boolean doValidate() {
if (onlyIfSaysToSkip()) {
return true;
}
return this.isValid();
}
}