/**
* DataCleaner (community edition)
* 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.datacleaner.result.renderer;
import java.util.Collection;
import org.datacleaner.api.Renderable;
import org.datacleaner.api.Renderer;
import org.datacleaner.api.RendererPrecedence;
import org.datacleaner.api.RenderingFormat;
import org.datacleaner.configuration.DataCleanerConfiguration;
import org.datacleaner.configuration.DataCleanerEnvironment;
import org.datacleaner.configuration.InjectionManager;
import org.datacleaner.descriptors.DescriptorProvider;
import org.datacleaner.descriptors.RendererBeanDescriptor;
import org.datacleaner.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 {
/**
* 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(final Renderer<?, ?> renderer, final RendererPrecedence precedence,
final 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 static final Logger logger = LoggerFactory.getLogger(RendererFactory.class);
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(final DataCleanerConfiguration configuration) {
this(configuration, defaultRendererInitializer(configuration));
}
public RendererFactory(final DataCleanerConfiguration configuration,
final RendererInitializer rendererInitializer) {
this(configuration.getEnvironment().getDescriptorProvider(), rendererInitializer);
}
public RendererFactory(final DataCleanerEnvironment environment, final RendererInitializer rendererInitializer) {
this(environment.getDescriptorProvider(), rendererInitializer);
}
/**
* Constructs a renderer factory
*
* @param descriptorProvider
* @param rendererInitializer
*
* @deprecated use {@link #RendererFactory(AnalyzerBeansConfiguration)}
* instead.
*/
@Deprecated
public RendererFactory(final DescriptorProvider descriptorProvider, final RendererInitializer rendererInitializer) {
_descriptorProvider = descriptorProvider;
_rendererInitializer = rendererInitializer;
}
private static RendererInitializer defaultRendererInitializer(final DataCleanerConfiguration configuration) {
final InjectionManager injectionManager =
configuration.getEnvironment().getInjectionManagerFactory().getInjectionManager(configuration);
return new DefaultRendererInitializer(injectionManager);
}
@SuppressWarnings("unchecked")
private static <I extends Renderable, O> Renderer<I, O> instantiate(final RendererBeanDescriptor<?> descriptor) {
final Class<? extends Renderer<?, ?>> componentClass = descriptor.getComponentClass();
final Renderer<?, ?> renderer = ReflectionUtils.newInstance(componentClass);
return (Renderer<I, O>) renderer;
}
/**
* 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(final I renderable,
final Class<? extends RenderingFormat<? extends O>> renderingFormat) {
RendererSelection bestMatch = null;
final Collection<RendererBeanDescriptor<?>> descriptors =
_descriptorProvider.getRendererBeanDescriptorsForRenderingFormat(renderingFormat);
for (final RendererBeanDescriptor<?> descriptor : descriptors) {
final 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") final 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(final RendererBeanDescriptor<?> rendererDescriptor,
final Renderable renderable, final RendererSelection bestMatch) {
final Class<? extends Renderable> renderableType = rendererDescriptor.getRenderableType();
final Class<? extends Renderable> renderableClass = renderable.getClass();
if (ReflectionUtils.is(renderableClass, renderableType)) {
if (bestMatch == null) {
return isRendererCapable(rendererDescriptor, renderable, bestMatch);
} else {
final int hierarchyDistance;
try {
hierarchyDistance = ReflectionUtils.getHierarchyDistance(renderableClass, renderableType);
} catch (final IllegalArgumentException e) {
logger.warn(
"Failed to determine hierarchy distance between renderable type '{}' and renderable of class '{}'",
renderableType, renderableClass, e);
return null;
}
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(final RendererBeanDescriptor<?> rendererDescriptor,
final Renderable renderable, final 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 (final 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);
}
}