package org.tessell.model.properties; import java.util.Map; import org.tessell.model.events.PropertyChangedEvent; import org.tessell.model.events.PropertyChangedHandler; import org.tessell.model.validation.events.RuleTriggeredHandler; import org.tessell.model.validation.events.RuleUntriggeredHandler; import org.tessell.model.validation.rules.Rule; import org.tessell.model.validation.rules.Static; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerRegistration; /** * Converts a source property to/from another type, using a {@link PropertyFormatter}. * * @param <SP> the source property type * @param <DP> the destination property type */ public class FormattedProperty<DP, SP> extends AbstractAbstractProperty<DP> { private final Property<SP> source; private final PropertyFormatter<SP, DP> formatter; private final Static isValid; public FormattedProperty(final Property<SP> source, final PropertyFormatter<SP, DP> formatter) { this(source, formatter, source.getName() + " is invalid"); } public FormattedProperty(final Property<SP> source, final PropertyFormatter<SP, DP> formatter, final String message) { this.source = source; this.formatter = formatter; // note, we currently fire the error against our source property, so that people listening for errors // to it will see them. it might make more sense to fire against us first, then our source property. isValid = new Static(message) { @Override public boolean isImportant() { return true; } }; source.addRule(isValid); source.addPropertyChangedHandler(new PropertyChangedHandler<SP>() { public void onPropertyChanged(PropertyChangedEvent<SP> event) { isValid.set(true); // any real (non-formatted) value being set makes our old attempt worthless } }); } @Override public String toString() { return source.getValueName() + " " + get(); } @Override public DP get() { SP value = source.get(); return (value == null) ? formatter.nullValue() : formatter.format(value); } @Override public void set(DP value) { set(value, true); } @Override public void set(DP value, boolean shouldTouch) { // null and "" are special if (value == null || "".equals(value)) { isValid.set(true); source.set(null, shouldTouch); return; } final SP parsed; try { parsed = formatter.parse(value); } catch (Exception e) { isValid.set(false); if (shouldTouch) { // we failed to parse the value, but still treat this as touching the source property source.setTouched(true); } return; } isValid.set(true); source.set(parsed, shouldTouch); } @Override public void setInitialValue(DP value) { set(value, false); } @Override public void setDefaultValue(DP value) { try { source.setDefaultValue(formatter.parse(value)); } catch (Exception e) { throw new RuntimeException("Default value cannot be parsed: " + value, e); } } @Override public DP getValue() { return get(); } @Override public void setValue(DP value) { set(value); } @Override public void setValue(DP value, boolean fire) { set(value); } @Override public void setIfNull(DP value) { if (get() == null) { set(value); } } /** * Changes the message shown when the formatter cannot parse input. * * Note: ensure to change this only initially, and not after any potentially error messages have been fired. */ public void setInvalidMessage(String message) { isValid.setMessage(message); } @Override public void reassess() { source.reassess(); } @Override @SuppressWarnings("unchecked") public void addRule(Rule<? super DP> rule) { source.addRule((Rule<? super SP>) rule); // hm, doesn't look right } @Override public boolean isTouched() { return source.isTouched(); } @Override public void setTouched(boolean touched) { source.setTouched(touched); } @Override public boolean touch() { return source.touch(); } @Override public boolean isValid() { return source.isValid(); } @Override public Property<Boolean> valid() { return source.valid(); } @Override public Property<Boolean> touched() { return source.touched(); } @Override public Property<Boolean> changed() { return source.changed(); } @Override public void fireEvent(GwtEvent<?> event) { source.fireEvent(event); } @Override public <T extends Property<?>> T addDerived(T downstream, Object token, boolean touch) { return source.addDerived(downstream, token, touch); } @Override public <T extends Property<?>> T removeDerived(T downstream, Object token) { return source.removeDerived(downstream, token); } @Override public Property<DP> depends(Property<?>... upstream) { source.depends(upstream); return this; } @Override public HandlerRegistration addPropertyChangedHandler(final PropertyChangedHandler<DP> handler) { return source.addPropertyChangedHandler(new PropertyChangedHandler<SP>() { public void onPropertyChanged(PropertyChangedEvent<SP> event) { DP oldValue = event.getOldValue() == null ? null : formatter.format(event.getOldValue()); DP newValue = event.getNewValue() == null ? null : formatter.format(event.getNewValue()); handler.onPropertyChanged(new PropertyChangedEvent<DP>(FormattedProperty.this, oldValue, newValue)); } }); } @Override public HandlerRegistration nowAndOnChange(final PropertyValueHandler<DP> handler) { return source.nowAndOnChange(new PropertyValueHandler<SP>() { public void onValue(SP value) { DP newValue = value == null ? null : formatter.format(value); handler.onValue(newValue); } }); } @Override public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<DP> handler) { return source.addValueChangeHandler(new ValueChangeHandler<SP>() { public void onValueChange(ValueChangeEvent<SP> event) { DP newValue = event.getValue() == null ? null : formatter.format(event.getValue()); // need an inner class because ValueChangedEvent's cstr is protected handler.onValueChange(new ValueChangeEvent<DP>(newValue) { }); } }); } @Override public HandlerRegistration addRuleTriggeredHandler(final RuleTriggeredHandler handler) { return source.addRuleTriggeredHandler(handler); } @Override public HandlerRegistration addRuleUntriggeredHandler(final RuleUntriggeredHandler handler) { return source.addRuleUntriggeredHandler(handler); } @Override public String getName() { // keep the same name for when formatted properties are put into // a FormPresenter, they keep the same name return source.getName(); } @Override public String getValueName() { return source.getValueName(); } @Override public Map<Object, String> getErrors() { return source.getErrors(); } @Override public boolean isReadOnly() { return false; } @Override public Property<Boolean> is(DP value) { throw new UnsupportedOperationException("Not implemented"); } @Override public Property<Boolean> is(DP value, DP whenUnsetValue) { throw new UnsupportedOperationException("Not implemented"); } @Override public Property<Boolean> is(Property<DP> value) { throw new UnsupportedOperationException("Not implemented"); } @Override public Property<Boolean> is(Property<DP> value, DP whenUnsetValue) { throw new UnsupportedOperationException("Not implemented"); } @Override public boolean isRequired() { return source.isRequired(); } @Override public void setRequired(boolean required) { source.setRequired(required); } }