package org.swellrt.model.generic; import org.swellrt.model.ReadableBoolean; import org.swellrt.model.ReadableNumber; import org.swellrt.model.ReadableTypeVisitor; import org.waveprotocol.wave.model.adt.ObservableBasicValue; import org.waveprotocol.wave.model.util.CopyOnWriteSet; import org.waveprotocol.wave.model.util.Preconditions; import org.waveprotocol.wave.model.wave.SourcesEvents; public class NumberType extends Type implements ReadableNumber, SourcesEvents<NumberType.Listener> { public interface Listener { void onValueChanged(String oldValue, String newValue); } /** * Get an instance of NumberType. This method is used for deserialization. * * @param parent the parent Type instance of this string * @param valueIndex the index of the value in the parent's value container * @return */ protected static NumberType deserialize(Type parent, String valueIndex) { NumberType string = new NumberType(); string.attach(parent, valueIndex); return string; } public final static String TYPE_NAME = "NumberType"; public final static String PREFIX = "n"; public final static String VALUE_ATTR = "v"; private ObservableBasicValue<String> observableValue; private ObservableBasicValue.Listener<String> observableValueListener; private String path; private Type parent; private int valueRef; // the index of this value in the ValuesContainer private String initValue; private boolean isAttached; private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create(); protected NumberType() { this.initValue = null; this.observableValueListener = new ObservableBasicValue.Listener<String>() { @Override public void onValueChanged(String oldValue, String newValue) { for (Listener l : listeners) l.onValueChanged(oldValue, newValue); } }; } public NumberType(double initValue) { // Use String.valueOf to be compatible with GWT init(String.valueOf(initValue)); } public NumberType(int initValue) { init(String.valueOf(initValue)); } public NumberType(String initValue) { init(initValue); } private void init(String initValue) { this.initValue = initValue != null ? initValue : ""; this.observableValueListener = new ObservableBasicValue.Listener<String>() { @Override public void onValueChanged(String oldValue, String newValue) { for (Listener l: listeners) l.onValueChanged(oldValue, newValue); } }; } @Override protected String getPrefix() { return PREFIX; } @Override protected void attach(Type parent) { Preconditions.checkArgument(parent.hasValuesContainer(), "Invalid parent type for a primitive value"); this.parent = parent; observableValue = parent.getValuesContainer().add(initValue); observableValue.addListener(observableValueListener); valueRef = parent.getValuesContainer().indexOf(observableValue); isAttached = true; } @Override protected void attach(Type parent, int slotIndex) { this.parent = parent; valueRef = slotIndex; if (initValue != null && !initValue.isEmpty()) observableValue = parent.getValuesContainer().add(initValue, slotIndex); else observableValue = parent.getValuesContainer().get(slotIndex); if (observableValue == null) { // return a non-attached value // this singals the actual value hasn't been received yet. return; } observableValue.addListener(observableValueListener); isAttached = true; } @Override protected void attach(Type parent, String valueIndex) { Preconditions.checkArgument(parent.hasValuesContainer(), "Invalid parent type for a primitive value"); Integer index = null; try { index = Integer.valueOf(valueIndex); } catch (NumberFormatException e) { } Preconditions.checkNotNull(index, "Value index is null"); attach(parent, index); } protected void deattach() { Preconditions.checkArgument(isAttached, "Unable to deattach an unattached NumberType"); observableValue.removeListener(this.observableValueListener); observableValue = null; isAttached = false; } @Override protected boolean isAttached() { return isAttached; } @Override protected String serialize() { Preconditions.checkArgument(isAttached, "Unable to serialize an unattached NumberType"); return PREFIX + "+" + Integer.toString(valueRef); } @Override protected ListElementInitializer getListElementInitializer() { return new ListElementInitializer() { @Override public String getType() { return PREFIX; } @Override public String getBackendId() { Preconditions.checkArgument(isAttached, "Unable to initialize an unattached NumberType"); return serialize(); } }; } // // Listeners // @Override public void addListener(Listener listener) { listeners.add(listener); } @Override public void removeListener(Listener listener) { listeners.remove(listener); } private boolean reAttach() { if (parent != null && valueRef >= 0) { attach(parent, valueRef); return isAttached(); } return false; } // // Number operations // public String getValue() { if (!isAttached()) if (!reAttach()) return initValue; return observableValue.get(); } public void setValue(double value) { setValue(Double.toString(value)); } public void setValue(int value) { setValue(Integer.toString(value)); } public void setValue(String value) { if (isAttached()) { if (!value.equals(observableValue.get())) { observableValue.set(value); parent.markValueUpdate(this); } } } @Override public String getDocumentId() { return null; } @Override public String getType() { return TYPE_NAME; } @Override protected void setPath(String path) { this.path = path; } @Override public String getPath() { return path; } @Override protected boolean hasValuesContainer() { return false; } @Override protected ValuesContainer getValuesContainer() { return null; } protected Integer getValueRefefence() { return valueRef; } @Override public Model getModel() { return parent.getModel(); } @Override public void accept(ReadableTypeVisitor visitor) { visitor.visit(this); } @Override public MapType asMap() { return null; } @Override public StringType asString() { return null; } @Override public ListType asList() { return null; } @Override public TextType asText() { return null; } @Override public FileType asFile() { return null; } @Override public ReadableNumber asNumber() { return this; } @Override public ReadableBoolean asBoolean() { return null; } @Override public Double getValueDouble() { try { return Double.parseDouble(getValue()); } catch (NumberFormatException e) { return null; } } @Override public Integer getValueInt() { try { return Integer.parseInt(getValue()); } catch (NumberFormatException e) { return null; } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((parent == null) ? 0 : parent.hashCode()); result = prime * result + valueRef; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; NumberType other = (NumberType) obj; if (parent == null) { if (other.parent != null) return false; } else if (!parent.equals(other.parent)) return false; if (valueRef != other.valueRef) return false; return true; } }