blob: 0514af1f048beb7c8ac38f6b4c47b19808637ef9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* $Id$ */
package org.apache.xmlgraphics.image.loader.pipeline;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.SortedSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.ImageManager;
import org.apache.xmlgraphics.image.loader.impl.CompositeImageLoader;
import org.apache.xmlgraphics.image.loader.spi.ImageConverter;
import org.apache.xmlgraphics.image.loader.spi.ImageImplRegistry;
import org.apache.xmlgraphics.image.loader.spi.ImageLoader;
import org.apache.xmlgraphics.image.loader.spi.ImageLoaderFactory;
import org.apache.xmlgraphics.util.dijkstra.DefaultEdgeDirectory;
import org.apache.xmlgraphics.util.dijkstra.DijkstraAlgorithm;
import org.apache.xmlgraphics.util.dijkstra.Vertex;
/**
* Factory class for image processing pipelines.
*/
public class PipelineFactory {
/** logger */
protected static Log log = LogFactory.getLog(PipelineFactory.class);
private ImageManager manager;
private int converterEdgeDirectoryVersion = -1;
/** Holds the EdgeDirectory for all image conversions */
private DefaultEdgeDirectory converterEdgeDirectory;
/**
* Main constructor.
* @param manager the ImageManager instance
*/
public PipelineFactory(ImageManager manager) {
this.manager = manager;
}
private DefaultEdgeDirectory getEdgeDirectory() {
ImageImplRegistry registry = manager.getRegistry();
if (registry.getImageConverterModifications() != converterEdgeDirectoryVersion) {
Collection converters = registry.getImageConverters();
//Rebuild edge directory
DefaultEdgeDirectory dir = new DefaultEdgeDirectory();
Iterator iter = converters.iterator();
while (iter.hasNext()) {
ImageConverter converter = (ImageConverter)iter.next();
dir.addEdge(new ImageConversionEdge(converter));
}
converterEdgeDirectoryVersion = registry.getImageConverterModifications();
this.converterEdgeDirectory = dir; //Replace (thread-safe)
}
return this.converterEdgeDirectory;
}
/**
* Creates and returns an {@link ImageProviderPipeline} that allows to load an image of the
* given MIME type and present it in the requested image flavor.
* @param originalImage the original image that serves as the origin point of the conversion
* @param targetFlavor the requested image flavor
* @return an {@link ImageProviderPipeline} or null if no suitable pipeline could be assembled
*/
public ImageProviderPipeline newImageConverterPipeline(
Image originalImage, ImageFlavor targetFlavor) {
//Get snapshot to avoid concurrent modification problems (thread-safety)
DefaultEdgeDirectory dir = getEdgeDirectory();
ImageRepresentation destination = new ImageRepresentation(targetFlavor);
ImageProviderPipeline pipeline = findPipeline(dir, originalImage.getFlavor(), destination);
return pipeline;
}
/**
* Creates and returns an {@link ImageProviderPipeline} that allows to load an image of the
* given MIME type and present it in the requested image flavor.
* @param imageInfo the image info object of the original image
* @param targetFlavor the requested image flavor
* @return an {@link ImageProviderPipeline} or null if no suitable pipeline could be assembled
*/
public ImageProviderPipeline newImageConverterPipeline(
ImageInfo imageInfo, ImageFlavor targetFlavor) {
String originalMime = imageInfo.getMimeType();
ImageImplRegistry registry = manager.getRegistry();
ImageProviderPipeline pipeline = null;
//Get snapshot to avoid concurrent modification problems (thread-safety)
DefaultEdgeDirectory dir = getEdgeDirectory();
ImageLoaderFactory[] loaderFactories = registry.getImageLoaderFactories(
imageInfo, targetFlavor);
if (loaderFactories != null) {
//Directly load image and return it
ImageLoader loader;
if (loaderFactories.length == 1) {
loader = loaderFactories[0].newImageLoader(targetFlavor);
} else {
int count = loaderFactories.length;
ImageLoader[] loaders = new ImageLoader[count];
for (int i = 0; i < count; i++) {
loaders[i] = loaderFactories[i].newImageLoader(targetFlavor);
}
loader = new CompositeImageLoader(loaders);
}
pipeline = new ImageProviderPipeline(manager.getCache(), loader);
} else {
//Need to use ImageConverters
if (log.isTraceEnabled()) {
log.trace("No ImageLoaderFactory found that can load this format directly."
+ " Trying ImageConverters instead...");
}
ImageRepresentation destination = new ImageRepresentation(targetFlavor);
//Get Loader for originalMIME
// --> List of resulting flavors, possibly multiple loaders
loaderFactories = registry.getImageLoaderFactories(originalMime);
if (loaderFactories != null) {
SortedSet candidates = new java.util.TreeSet(new PipelineComparator());
//Find best pipeline -> best loader
for (int i = 0, ci = loaderFactories.length; i < ci; i++) {
ImageLoaderFactory loaderFactory = loaderFactories[i];
ImageFlavor[] flavors = loaderFactory.getSupportedFlavors(originalMime);
for (int j = 0, cj = flavors.length; j < cj; j++) {
pipeline = findPipeline(dir, flavors[j], destination);
if (pipeline != null) {
ImageLoader loader = loaderFactory.newImageLoader(flavors[j]);
pipeline.setImageLoader(loader);
candidates.add(pipeline);
}
}
}
//Build final pipeline
if (candidates.size() > 0) {
pipeline = (ImageProviderPipeline)candidates.first();
}
}
}
if (pipeline != null && log.isDebugEnabled()) {
log.debug("Pipeline: " + pipeline + " with penalty " + pipeline.getConversionPenalty());
}
return pipeline;
}
private static class PipelineComparator implements Comparator {
public int compare(Object o1, Object o2) {
ImageProviderPipeline p1 = (ImageProviderPipeline)o1;
ImageProviderPipeline p2 = (ImageProviderPipeline)o2;
//Lowest penalty first
return p1.getConversionPenalty() - p2.getConversionPenalty();
}
}
private ImageProviderPipeline findPipeline(DefaultEdgeDirectory dir,
ImageFlavor originFlavor, ImageRepresentation destination) {
DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(
dir);
ImageRepresentation origin = new ImageRepresentation(originFlavor);
dijkstra.execute(origin, destination);
if (log.isTraceEnabled()) {
log.trace("Lowest penalty: " + dijkstra.getLowestPenalty(destination));
}
Vertex prev = destination;
Vertex pred = dijkstra.getPredecessor(destination);
if (pred == null) {
if (log.isTraceEnabled()) {
log.trace("No route found!");
}
return null;
} else {
LinkedList stops = new LinkedList();
while ((pred = dijkstra.getPredecessor(prev)) != null) {
ImageConversionEdge edge = (ImageConversionEdge)
dir.getBestEdge(pred, prev);
stops.addFirst(edge);
prev = pred;
}
ImageProviderPipeline pipeline = new ImageProviderPipeline(manager.getCache(), null);
Iterator iter = stops.iterator();
while (iter.hasNext()) {
ImageConversionEdge edge = (ImageConversionEdge)iter.next();
pipeline.addConverter(edge.getImageConverter());
}
return pipeline;
}
}
/**
* Finds and returns an array of {@link ImageProviderPipeline} instances which can handle
* the given MIME type and return one of the given {@link ImageFlavor}s.
* @param imageInfo the image info object
* @param flavors the possible target flavors
* @return an array of pipelines
*/
public ImageProviderPipeline[] determineCandidatePipelines(ImageInfo imageInfo,
ImageFlavor[] flavors) {
int count = flavors.length;
ImageProviderPipeline[] candidates = new ImageProviderPipeline[count];
for (int i = 0; i < count; i++) {
candidates[i] = newImageConverterPipeline(imageInfo, flavors[i]);
}
return candidates;
}
/**
* Finds and returns an array of {@link ImageProviderPipeline} instances which can handle
* the convert the given {@link Image} and return one of the given {@link ImageFlavor}s.
* @param sourceImage the image to be converted
* @param flavors the possible target flavors
* @return an array of pipelines
*/
public ImageProviderPipeline[] determineCandidatePipelines(Image sourceImage,
ImageFlavor[] flavors) {
int count = flavors.length;
ImageProviderPipeline[] candidates = new ImageProviderPipeline[count];
for (int i = 0; i < count; i++) {
candidates[i] = newImageConverterPipeline(sourceImage, flavors[i]);
}
return candidates;
}
}