/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.model.adt.docbased; import org.waveprotocol.wave.model.adt.ObservableBasicValue; import org.waveprotocol.wave.model.document.MutableDocument; import org.waveprotocol.wave.model.document.util.DocumentEventRouter; import org.waveprotocol.wave.model.util.AttributeListener; import org.waveprotocol.wave.model.util.CopyOnWriteSet; import org.waveprotocol.wave.model.util.Serializer; import org.waveprotocol.wave.model.util.ValueUtils; import java.util.Map; /** * Implementation of a basic value, on an attribute of a document element. * * TODO(user): unit test observable aspect. * * @param <E> type of a document element * @param <T> type of the basic value */ public final class DocumentBasedBasicValue<E, T> implements ObservableBasicValue<T>, AttributeListener<E> { /** Backing document service. */ private final MutableDocument<? super E, E, ?> document; /** Serializer for converting between attribute values and abstract values. */ private final Serializer<T> serializer; /** Name to use for the value attribute. */ private final String valueAttrName; /** Element holding the value. */ private final E element; /** Listeners */ private final CopyOnWriteSet<Listener<T>> listeners = CopyOnWriteSet.create(); /** * Creates a basic value. * * @param router router for the document holding the value state * @param element element on which the value state is stored * @param serializer converter between strings and values * @param valueAttrName name to use for value attributes */ public static <E, C extends Comparable<C>> DocumentBasedBasicValue<E, C> create( DocumentEventRouter<? super E, E, ?> router, E element, Serializer<C> serializer, String valueAttrName) { DocumentBasedBasicValue<E, C> value = new DocumentBasedBasicValue<E, C>( router.getDocument(), element, serializer, valueAttrName); router.addAttributeListener(element, value); return value; } /** * Creates an initializer for a basic value. * * @param serializer serializer for the value type * @param valueAttrName attribute name for the value * @param value the initial value */ public static <C> Initializer createInitialiser( final Serializer<C> serializer, final String valueAttrName, final C value) { return new Initializer() { @Override public void initialize(Map<String, String> target) { if (value != null) { Initializer.Helper.initialiseAttribute(target, valueAttrName, serializer.toString(value)); } } }; } /** * Creates a basic value. * * @see #create(DocumentEventRouter, Object, Serializer, String) */ private DocumentBasedBasicValue(MutableDocument<? super E, E, ?> document, E element, Serializer<T> serializer, String valueAttrName) { this.document = document; this.element = element; this.serializer = serializer; this.valueAttrName = valueAttrName; } @Override public T get() { String valueStr = document.getAttribute(element, valueAttrName); return serializer.fromString(valueStr); } @Override public void set(T value) { String valueStr = serializer.toString(value); document.setElementAttribute(element, valueAttrName, valueStr); } // // Listener stuff. // @Override public void onAttributesChanged( E element, Map<String, String> oldValues, Map<String, String> newValues) { assert element.equals(this.element) : "Received event for unrelated element"; String oldValueStr = oldValues.get(valueAttrName); String newValueStr = newValues.get(valueAttrName); T oldValue = serializer.fromString(oldValueStr); T newValue = serializer.fromString(newValueStr); if (ValueUtils.notEqual(oldValue, newValue)) { triggerOnValueChanged(oldValue, newValue); } } private void triggerOnValueChanged(T oldValue, T newValue) { for (Listener<T> listener : listeners) { listener.onValueChanged(oldValue, newValue); } } @Override public void addListener(Listener<T> listener) { listeners.add(listener); } @Override public void removeListener(Listener<T> listener) { listeners.remove(listener); } }