/**
* 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.ObservableStructuredValue;
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.CollectionUtils;
import org.waveprotocol.wave.model.util.CopyOnWriteSet;
import org.waveprotocol.wave.model.util.DeletionListener;
import org.waveprotocol.wave.model.util.Serializer;
import org.waveprotocol.wave.model.util.ValueUtils;
import java.util.Map;
/**
* Implementation of a structured value, on a set of attributes of a document
* element.
*
* The key type must override toString() to produce desired document attribute
* names.
*
* @param <E> type of a document element
* @param <K> enumerated type of the field names
* @param <V> field value type
* @author anorth@google.com (Alex North)
*/
public final class DocumentBasedStructuredValue<E, K extends Enum<K>, V> implements
ObservableStructuredValue<K, V>, AttributeListener<E>, DeletionListener {
/** Backing document service. */
private final MutableDocument<? super E, E, ?> document;
/** Element holding the value. */
private final E element;
/** Serializer for converting between attribute values and abstract values. */
private final Serializer<V> serializer;
/** Class object for the key enum type. */
private final Class<K> keyClass;
/** Listeners */
private final CopyOnWriteSet<Listener<K, ? super V>> listeners = CopyOnWriteSet.create();
/**
* Creates a structured 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 keyClass class object for the key type
*/
public static
<E, K extends Enum<K>, C extends Comparable<C>> DocumentBasedStructuredValue<E, K, C> create(
DocumentEventRouter<? super E, E, ?> router, E element, Serializer<C> serializer,
Class<K> keyClass) {
DocumentBasedStructuredValue<E, K, C> value =
new DocumentBasedStructuredValue<E, K, C>(router.getDocument(), element,
serializer, keyClass);
router.addAttributeListener(element, value);
router.addDeletionListener(element, value);
return value;
}
/**
* Creates an initialiser for a structured value.
*
* @param serializer serializer for the value type
* @param initialValues initial values
*/
public static <K extends Enum<K>, C extends Comparable<C>> Initializer createInitialiser(
final Serializer<C> serializer, final Map<K, C> initialValues) {
return new Initializer() {
@Override
public void initialize(Map<String, String> target) {
for (Map.Entry<K, C> entry : initialValues.entrySet()) {
if (entry.getValue() != null) {
Initializer.Helper.initialiseAttribute(target, entry.getKey().toString(),
serializer.toString(entry.getValue()));
}
}
}
};
}
/**
* Creates a structured value.
*
* @see #create(DocumentEventRouter, Object, Serializer, Class)
*/
private DocumentBasedStructuredValue(MutableDocument<? super E, E, ?> document, E element,
Serializer<V> serializer, Class<K> keyClass) {
this.document = document;
this.element = element;
this.serializer = serializer;
this.keyClass = keyClass;
}
@Override
public V get(K name) {
String valueStr = document.getAttribute(element, name.toString());
return serializer.fromString(valueStr);
}
@Override
public void set(K name, V value) {
String valueStr = serializer.toString(value);
document.setElementAttribute(element, name.toString(), valueStr);
}
@Override
public void set(Map<K, V> values) {
Map<String, String> valueStrings = CollectionUtils.newHashMap();
for (Map.Entry<K, V> entry : values.entrySet()) {
String valueStr = serializer.toString(entry.getValue());
valueStrings.put(entry.getKey().toString(), valueStr);
}
document.updateElementAttributes(element, valueStrings);
}
//
// Listener stuff.
//
@Override
public void onAttributesChanged(E element, final Map<String, String> oldValues,
final Map<String, String> newValues) {
assert element.equals(this.element) : "Received event for unrelated element";
final Map<K, V> oldFields = CollectionUtils.newHashMap();
final Map<K, V> newFields = CollectionUtils.newHashMap();
K[] names = keyClass.getEnumConstants();
for (K name : names) {
if (oldValues.containsKey(name.toString()) || newValues.containsKey(name.toString())) {
String oldValueStr = oldValues.get(name.toString());
String newValueStr = newValues.get(name.toString());
V oldValue = serializer.fromString(oldValueStr);
V newValue = serializer.fromString(newValueStr);
if (ValueUtils.notEqual(oldValue, newValue)) {
oldFields.put(name, oldValue);
newFields.put(name, newValue);
}
}
}
assert oldFields.keySet().equals(newFields.keySet());
if (!oldFields.isEmpty()) {
triggerOnValuesChanged(oldFields, newFields);
}
}
public void onDeleted() {
// TODO(anorth): detach listeners when EventPlumber makes that possible.
triggerOnDeleted();
}
private void triggerOnValuesChanged(Map<K, V> oldValues, Map<K, V> newValues) {
for (Listener<K, ? super V> listener : listeners) {
listener.onValuesChanged(oldValues, newValues);
}
}
private void triggerOnDeleted() {
for (Listener<K, ? super V> listener : listeners) {
listener.onDeleted();
}
}
@Override
public void addListener(Listener<K, ? super V> listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener<K, ? super V> listener) {
listeners.remove(listener);
}
}