/*******************************************************************************
* Copyright 2013 Geoscience Australia
*
* 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 au.gov.ga.earthsci.intent.dispatch;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.gov.ga.earthsci.common.collection.ArrayListTreeMap;
import au.gov.ga.earthsci.common.collection.ListSortedMap;
import au.gov.ga.earthsci.intent.Intent;
import au.gov.ga.earthsci.intent.util.ContextInjectionFactoryThreadSafe;
/**
* Central mechanism for handling unexpected domain objects. This is inherently
* tied to the intent system in that, if an intent handler produces a result
* that the intent caller didn't expect, the caller can (optionally, perhaps
* after prompting the user) pass the result to the {@link Dispatcher} to
* gracefully handle the result.
* <p/>
* For example, if the layer model loads a file, but an object other than a
* layer is returned (eg a catalog node), it can pass the object to the
* {@link Dispatcher} to attempt to handle the object gracefully.
* <p/>
* UI components and plugins can define {@link IDispatchHandler}s via an
* extension point which handle instances of specific classes.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class Dispatcher
{
private static final String DISPATCH_FILTER_ID = "au.gov.ga.earthsci.intent.dispatchFilters"; //$NON-NLS-1$
private static final Logger logger = LoggerFactory.getLogger(Dispatcher.class);
private static Dispatcher instance = new Dispatcher();
public static Dispatcher getInstance()
{
return instance;
}
//filters, sorted descending by priority
private final ListSortedMap<Integer, DispatchFilter> filters = new ArrayListTreeMap<Integer, DispatchFilter>(
new Comparator<Integer>()
{
@Override
public int compare(Integer o1, Integer o2)
{
return -o1.compareTo(o2);
}
});
private Dispatcher()
{
IConfigurationElement[] config = RegistryFactory.getRegistry().getConfigurationElementsFor(DISPATCH_FILTER_ID);
for (IConfigurationElement element : config)
{
try
{
boolean isFilter = "filter".equals(element.getName()); //$NON-NLS-1$
if (isFilter)
{
DispatchFilter filter = new DispatchFilter(element);
filters.putSingle(filter.getPriority(), filter);
}
}
catch (Exception e)
{
logger.error("Error processing dispatch filter", e); //$NON-NLS-1$
}
}
}
/**
* Dispatch the given object to a dispatch handler if a matching filter can
* be found for the object's class. The dispatch handler is instantiated
* using the {@link ContextInjectionFactory}, which injects the handler
* using the provided context.
*
* @param object
* Object to dispatch
* @param intent
* Intent that loaded the dispatched object, <code>null</code> if
* object is not the result of an intent
* @param context
* Context to use for injection into the handler
* @return True if a dispatch handler was found to handle the object
*/
public boolean dispatch(Object object, Intent intent, IEclipseContext context)
{
if (object == null)
{
return false;
}
DispatchFilter filter = findFilter(object);
Class<? extends IDispatchHandler> handlerClass = filter == null ? null : filter.getHandler();
if (handlerClass == null)
{
logger.error("Could not find dispatch handler for object: " + object); //$NON-NLS-1$
return false;
}
IEclipseContext activeLeaf = context.getActiveLeaf();
IEclipseContext child = activeLeaf.createChild();
IDispatchHandler handler = ContextInjectionFactoryThreadSafe.make(handlerClass, child);
handler.handle(object, intent);
return true;
}
/**
* Find the best dispatch filter for the given object's class.
*
* @param object
* Object to find a filter for
* @return Dispatch filter for the object's class, or null if no matching
* filter could be found
*/
public DispatchFilter findFilter(Object object)
{
if (object == null)
{
return null;
}
int minDistance = Integer.MAX_VALUE;
DispatchFilter closest = null;
for (List<DispatchFilter> list : filters.values())
{
for (DispatchFilter filter : list)
{
int distance = distanceToClosestType(object.getClass(), filter.getTypes());
if (distance >= 0 && distance < minDistance)
{
minDistance = distance;
closest = filter;
}
}
}
return closest;
}
protected static int distanceToClosestType(Class<?> subclass, Collection<Class<?>> superclasses)
{
int minDistance = Integer.MAX_VALUE;
for (Class<?> superclass : superclasses)
{
int distance = classHierarchyDistance(subclass, superclass);
if (distance >= 0 && distance < minDistance)
{
minDistance = distance;
}
}
return minDistance == Integer.MAX_VALUE ? -1 : minDistance;
}
protected static int classHierarchyDistance(Class<?> subclass, Class<?> superclass)
{
if (subclass == null || superclass == null)
{
return -1;
}
if (!superclass.isAssignableFrom(subclass))
{
return -1;
}
return classHierarchyDistance(subclass, superclass, 0);
}
private static int classHierarchyDistance(Class<?> subclass, Class<?> superclass, int position)
{
if (subclass.equals(superclass))
{
return position;
}
position++;
Class<?>[] supers = subclass.getInterfaces();
if (subclass.getSuperclass() != null)
{
Class<?>[] newSupers = new Class<?>[supers.length + 1];
System.arraycopy(supers, 0, newSupers, 1, supers.length);
supers = newSupers;
supers[0] = subclass.getSuperclass();
}
int minDistance = Integer.MAX_VALUE;
for (Class<?> s : supers)
{
int distance = classHierarchyDistance(s, superclass, position);
if (distance >= 0 && distance < minDistance)
{
minDistance = distance;
}
}
return minDistance == Integer.MAX_VALUE ? -1 : minDistance;
}
}