/**
* Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
* All rights reserved. Use is subject to license terms. See LICENSE.TXT
*/
package org.diirt.datasource;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implements the mechanism for registering different types so that the library
* knows how to handle them.
* <p>
* For a type to be usable by the library it needs to be defined:
* <ul>
* <li>How to copy - since values given to the UI should be modified only
* within the UI thread, it follows that new values cannot be prepared
* "in place", on the same object that was given to the UI. At notification,
* there will be then two copies, the old and the new, and in need to be clear
* how the new copy should be delivered. (e.g. just pass the new copy, modify
* the old object in place, etc...).</li>
* <li>When to notify - by comparing elements of the value, it should
* decide on what condition the old value need to be modified and the
* UI should be notified of the change.</li>
* </ul>
*
* @param <T> the type for which to add support
* @author carcassi
*/
public abstract class TypeSupport<T> {
/**
* Internal class to improve readability.
* @author bknerr
* @since 20.01.2011
*/
private static final class TypeSupportMap extends ConcurrentHashMap<Class, TypeSupport> {
private static final long serialVersionUID = -8726785703555122582L;
public TypeSupportMap() { /* EMPTY */ }
}
private static final Map<Class<? extends TypeSupport>, TypeSupportMap> allTypeSupports =
new ConcurrentHashMap<Class<? extends TypeSupport>, TypeSupportMap>();
private static final Map<Class<? extends TypeSupport>, TypeSupportMap> allCalcTypeSupports =
new ConcurrentHashMap<Class<? extends TypeSupport>, TypeSupportMap>();
private static
void addTypeSupportFamilyIfNotExists(final Map<Class<? extends TypeSupport>, TypeSupportMap> map,
final Class<? extends TypeSupport> typeSupportFamily) {
TypeSupportMap familyMap = map.get(typeSupportFamily);
if (familyMap == null) {
TypeSupportMap supportMap = new TypeSupportMap();
map.put(typeSupportFamily, supportMap);
}
}
/**
* Adds type support for the given class. The type support added will apply
* to the given class and all of its subclasses. Support of the same
* family cannot be added twice and will cause an exception. Support for
* the more specific subclass overrides support for the more abstract class.
* A class cannot have two types support in the same family coming from
* two different and unrelated interfaces.
*
* @param typeSupport the support to add
*/
public static
void addTypeSupport(final TypeSupport<?> typeSupport) {
Class<? extends TypeSupport> typeSupportFamily = typeSupport.getTypeSupportFamily();
addTypeSupportFamilyIfNotExists(allTypeSupports, typeSupportFamily);
addTypeSupportFamilyIfNotExists(allCalcTypeSupports, typeSupportFamily);
// Can't install support for the same type twice
if (allTypeSupports.get(typeSupportFamily).get(typeSupport.getType()) != null) {
throw new RuntimeException(typeSupportFamily.getSimpleName() + " was already added for type " + typeSupport.getType().getName());
}
allTypeSupports.get(typeSupportFamily).put(typeSupport.getType(), typeSupport);
// Need to clear all calculated supports since registering an
// interface may affect all the calculated supports
// of all the implementations
allCalcTypeSupports.get(typeSupportFamily).clear();
}
/**
* Checks whether the type is supported directly or through one of the
* supertypes.
*
* @param type type to check support for
* @param typeSupportFamily the family in which to look for
* @return true if supported
* @throws RuntimeException if multiple conflicting support is found
*/
public static boolean isTypeSupported(Class<? extends TypeSupport> typeSupportFamily, Class<?> type) {
return findTypeSupportFor(typeSupportFamily, type) != null;
}
/**
* Checks whether the type is supported on the same exact type.
*
* @param type type to check support for
* @param typeSupportFamily the family in which to look for
* @return true if supported
*/
public static boolean isTypeDirectlySupported(Class<? extends TypeSupport> typeSupportFamily, Class<?> type) {
return allTypeSupports.get(typeSupportFamily) != null &&
allTypeSupports.get(typeSupportFamily).get(type) != null;
}
/**
* Calculates and caches the type support for a particular class, so that
* introspection does not occur at every call.
* <p>
* Find the supports for all supertypes. If multiple supports for different
* supertypes are found, and there isn't a most specific one (i.e. one
* that implements all the others) then an exception is thrown.
*
* @param <T> the type to retrieve support for
* @param supportFamily the support family for which to find support
* @param typeClass the class of the type
* @return the support for the type or null
* @throws RuntimeException if multiple conflicting support is found
*/
protected static <T> TypeSupport<T> findTypeSupportFor(final Class<? extends TypeSupport> supportFamily,
final Class<T> typeClass) {
TypeSupportMap calcSupportMap = allCalcTypeSupports.get(supportFamily);
TypeSupportMap supportMap = allTypeSupports.get(supportFamily);
if (supportMap == null || calcSupportMap == null) {
return null;
}
// If we get the cached support for a specific type,
// we are guaranteed that they support is for that type
@SuppressWarnings("unchecked")
TypeSupport<T> support = (TypeSupport<T>) calcSupportMap.get(typeClass);
if (support == null) {
support = calculateSupport(typeClass, supportMap);
if (support == null) {
// It's up to the specific support to decide what to do
return null;
}
calcSupportMap.put(typeClass, support);
}
return support;
}
/**
* Returns all the type supports available for the given class.
*
* @param <T> the kind of type support
* @param supportFamily the supported type
* @return the list of type supports for the given type
*/
protected static <T extends TypeSupport<?>> Collection<T> typeSupportsFor(final Class<T> supportFamily) {
TypeSupportMap map = allTypeSupports.get(supportFamily);
if (map == null)
return Collections.emptyList();
@SuppressWarnings("unchecked")
Collection<T> supports = (Collection<T>) (Collection) map.values();
return supports;
}
private static <T> TypeSupport<T> calculateSupport(final Class<T> typeClass,
final TypeSupportMap supportMap) {
// Get all super types that have a support defined on
Set<Class> superTypes = new HashSet<Class>();
recursiveAddAllSuperTypes(typeClass, superTypes);
superTypes.retainAll(supportMap.keySet());
// No super type found, no support for this type
if (superTypes.isEmpty()) {
return null;
}
// Super types found, make sure that there is one
// type that implements everything
for (Class<?> type : superTypes) {
boolean assignableToEverything = true;
for (Class<?> compareType : superTypes) {
assignableToEverything = assignableToEverything && compareType.isAssignableFrom(type);
}
if (assignableToEverything) {
// The introspection above guarantees that the type
// support is of a compatible type
@SuppressWarnings("unchecked")
TypeSupport<T> support = (TypeSupport<T>) supportMap.get(type);
return support;
}
}
throw new RuntimeException("Multiple support for type " + typeClass + " through " + superTypes);
}
private static void recursiveAddAllSuperTypes(Class clazz, Set<Class> superClasses) {
// If already visited or null , return
if (clazz == null || superClasses.contains(clazz)) {
return;
}
superClasses.add(clazz);
recursiveAddAllSuperTypes(clazz.getSuperclass(), superClasses);
for (Class interf : clazz.getInterfaces()) {
recursiveAddAllSuperTypes(interf, superClasses);
}
}
/**
* Creates a new type support of the given type
*
* @param type the type on which support is defined
* @param typeSupportFamily the kind of support is being defined
*/
public TypeSupport(Class<T> type, Class<? extends TypeSupport> typeSupportFamily) {
this.type = type;
this.typeSupportFamily = typeSupportFamily;
}
// Type on which the support is defined
private final Class<T> type;
// Which kind of type support is defined
private final Class<? extends TypeSupport> typeSupportFamily;
/**
* Defines which type of support is implementing, notification or time.
*
* @return the support family
*/
protected Class<? extends TypeSupport> getTypeSupportFamily() {
return typeSupportFamily;
}
/**
* Defines on which class the support is defined.
*
* @return the type of the class
*/
protected Class<T> getType() {
return type;
}
}