// Copyright 2009 Google Inc. All Rights Reserved.
package org.waveprotocol.wave.model.document.util;
import org.waveprotocol.wave.model.util.ChainedData;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.DataDomain;
import org.waveprotocol.wave.model.util.IdentityMap;
import org.waveprotocol.wave.model.util.StringMap;
/**
* Registry for different types of handlers for document elements, based
* on a match condition. Currently just tag name plus special attribute
* matching is supported.
*
* There is a limitation in this approach, namely that handler subclasses
* are not recognised - so, if you call getHandler to get a handler, you will
* only get a result when passing in the exact .class with which it was
* registered. For example, if you subclass Renderer, you'll probably need
* to register the renderer twice, both with Renderer.class and YourRenderer.class,
* or do type casting on the return value from Renderer.class.
*
* TODO(danilatos): Conceive of a nicer registering mechanism than class plus
* tagname plus attribute
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class ElementHandlerRegistry {
/**
* The minimum interface required by the registry to identify an element in
* order to ascertain look up its handlers.
*/
public interface HasHandlers {
String getTagName();
}
private static class HandlerData {
/**
* Our map of handlers for different element types
*/
StringMap<IdentityMap<Class<Object>, Object>> handlers = CollectionUtils.createStringMap();
}
private static final DataDomain<HandlerData, HandlerData> handlerDataDomain =
new DataDomain<HandlerData, HandlerData>() {
@Override
public void compose(HandlerData target, HandlerData changes, HandlerData base) {
target.handlers.clear();
copyInto(target, base);
copyInto(target, changes);
}
private void copyInto(final HandlerData target, HandlerData source) {
source.handlers.each(new StringMap.ProcV<IdentityMap<Class<Object>, Object>>() {
@Override
public void apply(final String key,
IdentityMap<Class<Object>, Object> sourceElemHandlers) {
final IdentityMap<Class<Object>, Object> destElementHandlers =
target.handlers.containsKey(key)
? target.handlers.get(key)
: CollectionUtils.<Class<Object>, Object>createIdentityMap();
sourceElemHandlers.each(new IdentityMap.ProcV<Class<Object>, Object>() {
public void apply(Class<Object> key, Object handler) {
destElementHandlers.put(key, handler);
}
});
target.handlers.put(key, destElementHandlers);
}
});
}
@Override
public HandlerData empty() {
return new HandlerData();
}
@Override
public HandlerData readOnlyView(HandlerData modifiable) {
return modifiable;
}
};
/**
* The singleton registry that, as the terminal of the priority chain, is
* always consulted last by any registry.
*/
public static final ElementHandlerRegistry ROOT = new ElementHandlerRegistry();
private final ChainedData<HandlerData, HandlerData> data;
private ElementHandlerRegistry() {
data = new ChainedData<HandlerData, HandlerData>(handlerDataDomain);
}
/**
* @param parent parent registry in the chain
*/
private ElementHandlerRegistry(ElementHandlerRegistry parent) {
data = new ChainedData<HandlerData, HandlerData>(parent.data);
}
/**
* @return a chained child
*/
public ElementHandlerRegistry createExtension() {
return new ElementHandlerRegistry(this);
}
/**
* Registers a handler.
*
* @param <T> The handler type
* @param handlerType The .class of the handler type
* @param ns Element namespace. May be null
* @param tag Element tag name
* @param typeAttr Special type attribute (currently "_t"). May be null.
* @param handler The handler to register
*/
@SuppressWarnings("unchecked")
public <T> void register(Class<T> handlerType, String ns, String tag, String typeAttr,
T handler) {
String id = getElementIdentifier(ns, tag, typeAttr);
IdentityMap<Class<Object>, Object> handlersForElement = data.modify().handlers.get(id);
if (handlersForElement == null) {
handlersForElement = CollectionUtils.createIdentityMap();
data.modify().handlers.put(id, handlersForElement);
}
handlersForElement.put((Class<Object>) handlerType, (Object)handler);
}
/**
* Get the handler for the given element.
*
* @param element The element
* @param typeAttrVal additional subtype attribute value
* @param handlerType The .class of the handler
* @return The handler instance, or null if none available
*/
@SuppressWarnings("unchecked")
public <T> T getHandler(HasHandlers element, String typeAttrVal, Class<T> handlerType) {
String id = element.getTagName();
if (typeAttrVal != null) {
id += "." + typeAttrVal;
}
IdentityMap<Class<Object>, Object> handlersForElement = data.inspect().handlers.get(id);
if (handlersForElement == null) {
return null;
}
return (T) handlersForElement.get((Class<Object>) handlerType);
}
private String getElementIdentifier(String ns, String tag, String typeAttr) {
return (ns != null ? ns + ":" : "") + tag + (typeAttr != null ? "." + typeAttr : "");
}
}