/**
* AnalyzerBeans
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.eobjects.analyzer.result.renderer;
import java.util.Collection;
import org.eobjects.analyzer.beans.api.Renderer;
import org.eobjects.analyzer.beans.api.RendererPrecedence;
import org.eobjects.analyzer.beans.api.RenderingFormat;
import org.eobjects.analyzer.configuration.AnalyzerBeansConfiguration;
import org.eobjects.analyzer.descriptors.DescriptorProvider;
import org.eobjects.analyzer.descriptors.RendererBeanDescriptor;
import org.eobjects.analyzer.util.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A factory that can resolve the best suited {@link Renderer} for a particular
* {@link Renderable}. The resolving mechanism inspects the available renderers
* in the {@link DescriptorProvider}.
*
*
*/
public final class RendererFactory {
private static final Logger logger = LoggerFactory.getLogger(RendererFactory.class);
/**
* Represents a selection of a renderer. Will be used to store everything
* related to a renderer while finding the most suited renderer.
*
*
*
*/
private static class RendererSelection {
private final Renderer<?, ?> renderer;
private final RendererPrecedence precedence;
private final int hierarchyDistance;
public RendererSelection(Renderer<?, ?> renderer, RendererPrecedence precedence, int hierarchyDistance) {
this.renderer = renderer;
this.precedence = precedence;
this.hierarchyDistance = hierarchyDistance;
}
public Renderer<?, ?> getRenderer() {
return renderer;
}
public RendererPrecedence getPrecedence() {
return precedence;
}
public int getHierarchyDistance() {
return hierarchyDistance;
}
}
private final DescriptorProvider _descriptorProvider;
private final RendererInitializer _rendererInitializer;
/**
* Constructs a renderer factory
*
* @param configuration
* @param job
* optionally a job which the instantiated renderers pertain to.
*/
public RendererFactory(AnalyzerBeansConfiguration configuration) {
this(configuration, new DefaultRendererInitializer(configuration));
}
public RendererFactory(AnalyzerBeansConfiguration configuration, RendererInitializer rendererInitializer) {
this(configuration.getDescriptorProvider(), rendererInitializer);
}
/**
* Constructs a renderer factory
*
* @param descriptorProvider
* @param rendererInitializer
*
* @deprecated use
* {@link #RendererFactory(AnalyzerBeansConfiguration)}
* instead.
*/
@Deprecated
public RendererFactory(DescriptorProvider descriptorProvider, RendererInitializer rendererInitializer) {
_descriptorProvider = descriptorProvider;
_rendererInitializer = rendererInitializer;
}
/**
* Gets the best suited {@link Renderer} for the given {@link Renderable} to
* the given {@link RenderingFormat}.
*
* @param <I>
* @param <O>
* @param renderable
* @param renderingFormat
* @return
*/
public <I extends Renderable, O> Renderer<? super I, ? extends O> getRenderer(I renderable,
Class<? extends RenderingFormat<? extends O>> renderingFormat) {
RendererSelection bestMatch = null;
Collection<RendererBeanDescriptor<?>> descriptors = _descriptorProvider
.getRendererBeanDescriptorsForRenderingFormat(renderingFormat);
for (RendererBeanDescriptor<?> descriptor : descriptors) {
RendererSelection rendererMatch = isRendererMatch(descriptor, renderable, bestMatch);
if (rendererMatch != null) {
bestMatch = rendererMatch;
}
}
if (bestMatch == null) {
logger.warn("Didn't find any matches for renderable {} (format={})", renderable, renderingFormat);
return null;
}
@SuppressWarnings("unchecked")
Renderer<? super I, ? extends O> renderer = (Renderer<? super I, ? extends O>) bestMatch.getRenderer();
if (logger.isInfoEnabled()) {
logger.info("Returning renderer '{}' for renderable '{}' in format '{}'", new Object[] { renderer,
renderable.getClass().getName(), renderingFormat.getName() });
}
return renderer;
}
/**
* Checks if a particular renderer (descriptor) is a good match for a
* particular renderer.
*
* @param rendererDescriptor
* the renderer (descriptor) to check.
* @param renderable
* the renderable that needs rendering.
* @param bestMatchingDescriptor
* the currently "best matching" renderer (descriptor), or null
* if no other renderers matches yet.
* @return a {@link RendererSelection} object if the renderer is a match, or
* null if not.
*/
private RendererSelection isRendererMatch(RendererBeanDescriptor<?> rendererDescriptor, Renderable renderable,
RendererSelection bestMatch) {
final Class<? extends Renderable> renderableType = rendererDescriptor.getRenderableType();
if (ReflectionUtils.is(renderable.getClass(), renderableType)) {
if (bestMatch == null) {
return isRendererCapable(rendererDescriptor, renderable, bestMatch);
} else {
int hierarchyDistance = ReflectionUtils.getHierarchyDistance(renderable.getClass(), renderableType);
if (hierarchyDistance == 0) {
// no hierarchy distance
return isRendererCapable(rendererDescriptor, renderable, bestMatch);
}
if (hierarchyDistance <= bestMatch.getHierarchyDistance()) {
// lower hierarchy distance than best match
return isRendererCapable(rendererDescriptor, renderable, bestMatch);
}
}
}
return null;
}
private RendererSelection isRendererCapable(RendererBeanDescriptor<?> rendererDescriptor, Renderable renderable,
RendererSelection bestMatch) {
final Renderer<Renderable, ?> renderer = instantiate(rendererDescriptor);
if (_rendererInitializer != null) {
_rendererInitializer.initialize(rendererDescriptor, renderer);
}
RendererPrecedence precedence;
try {
precedence = renderer.getPrecedence(renderable);
if (precedence == null) {
logger.debug("Renderer precedence was null for {}, using MEDIUM", renderer);
precedence = RendererPrecedence.MEDIUM;
}
if (precedence == RendererPrecedence.NOT_CAPABLE) {
logger.debug("Renderer is not capable of rendering this renderable!");
return null;
}
if (bestMatch != null) {
final RendererPrecedence bestPrecedence = bestMatch.getPrecedence();
if (precedence.ordinal() < bestPrecedence.ordinal()) {
logger.info("Precedence {} did not match or supersede best matching precedence ({}).", precedence, bestPrecedence);
return null;
}
}
} catch (Exception e) {
logger.error("Could not get precedence of renderer, returning null", e);
return null;
}
final Class<? extends Renderable> renderableType = rendererDescriptor.getRenderableType();
final int hierarchyDistance = ReflectionUtils.getHierarchyDistance(renderable.getClass(), renderableType);
return new RendererSelection(renderer, precedence, hierarchyDistance);
}
@SuppressWarnings("unchecked")
private static <I extends Renderable, O> Renderer<I, O> instantiate(RendererBeanDescriptor<?> descriptor) {
final Class<? extends Renderer<?, ?>> componentClass = descriptor.getComponentClass();
final Renderer<?, ?> renderer = ReflectionUtils.newInstance(componentClass);
return (Renderer<I, O>) renderer;
}
}