/*
* 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: PipelineFactory.java 1804124 2017-08-04 14:13:54Z ssteiner $ */
package org.apache.xmlgraphics.image.loader.pipeline;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
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.image.loader.util.Penalty;
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.
/**
* Factory class for image processing pipelines.
*/
public class PipelineFactory {
logger /** logger */
protected static final Log log = LogFactory.getLog(PipelineFactory.class);
private ImageManager manager;
private int converterEdgeDirectoryVersion = -1;
Holds the EdgeDirectory for all image conversions /** Holds the EdgeDirectory for all image conversions */
private DefaultEdgeDirectory converterEdgeDirectory;
Main constructor.
Params: - manager – the ImageManager instance
/**
* 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();
for (Object converter1 : converters) {
ImageConverter converter = (ImageConverter) converter1;
Penalty penalty = Penalty.toPenalty(converter.getConversionPenalty());
penalty = penalty.add(
registry.getAdditionalPenalty(converter.getClass().getName()));
dir.addEdge(new ImageConversionEdge(converter, penalty));
}
converterEdgeDirectoryVersion = registry.getImageConverterModifications();
this.converterEdgeDirectory = dir; //Replace (thread-safe)
}
return this.converterEdgeDirectory;
}
Creates and returns an ImageProviderPipeline
that allows to load an image of the given MIME type and present it in the requested image flavor. Params: - originalImage – the original image that serves as the origin point of the conversion
- targetFlavor – the requested image flavor
Returns: an ImageProviderPipeline
or null if no suitable pipeline could be assembled
/**
* 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 ImageProviderPipeline
that allows to load an image of the given MIME type and present it in the requested image flavor. Params: - imageInfo – the image info object of the original image
- targetFlavor – the requested image flavor
Returns: an ImageProviderPipeline
or null if no suitable pipeline could be assembled
/**
* 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) {
ImageProviderPipeline[] candidates = determineCandidatePipelines(imageInfo, targetFlavor);
//Choose best pipeline
if (candidates.length > 0) {
Arrays.sort(candidates, new PipelineComparator());
ImageProviderPipeline pipeline = (ImageProviderPipeline)candidates[0];
if (pipeline != null && log.isDebugEnabled()) {
log.debug("Pipeline: " + pipeline
+ " with penalty " + pipeline.getConversionPenalty());
}
return pipeline;
} else {
return null;
}
}
Determines all possible pipelines for the given image that can produce the requested
target flavor.
Params: - imageInfo – the image information
- targetFlavor – the target flavor
Returns: the candidate pipelines
/**
* Determines all possible pipelines for the given image that can produce the requested
* target flavor.
* @param imageInfo the image information
* @param targetFlavor the target flavor
* @return the candidate pipelines
*/
public ImageProviderPipeline[] determineCandidatePipelines(
ImageInfo imageInfo, ImageFlavor targetFlavor) {
String originalMime = imageInfo.getMimeType();
ImageImplRegistry registry = manager.getRegistry();
List candidates = new java.util.ArrayList();
//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);
}
ImageProviderPipeline pipeline = new ImageProviderPipeline(manager.getCache(), loader);
candidates.add(pipeline);
} else {
//Need to use ImageConverters
if (log.isTraceEnabled()) {
log.trace("No ImageLoaderFactory found that can load this format ("
+ targetFlavor + ") 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) {
//Find best pipeline -> best loader
for (ImageLoaderFactory loaderFactory : loaderFactories) {
ImageFlavor[] flavors = loaderFactory.getSupportedFlavors(originalMime);
for (ImageFlavor flavor : flavors) {
ImageProviderPipeline pipeline = findPipeline(dir, flavor, destination);
if (pipeline != null) {
ImageLoader loader = loaderFactory.newImageLoader(flavor);
pipeline.setImageLoader(loader);
candidates.add(pipeline);
}
}
}
}
}
return (ImageProviderPipeline[])candidates.toArray(
new ImageProviderPipeline[candidates.size()]);
}
Compares two pipelines based on their conversion penalty. /** Compares two pipelines based on their conversion penalty. */
private static class PipelineComparator implements Comparator, Serializable {
private static final long serialVersionUID = 1161513617996198090L;
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);
for (Object stop : stops) {
ImageConversionEdge edge = (ImageConversionEdge) stop;
pipeline.addConverter(edge.getImageConverter());
}
return pipeline;
}
}
Finds and returns an array of ImageProviderPipeline
instances which can handle the given MIME type and return one of the given ImageFlavor
s. Params: - imageInfo – the image info object
- flavors – the possible target flavors
Returns: an array of pipelines
/**
* 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) {
List candidates = new java.util.ArrayList();
for (ImageFlavor flavor : flavors) {
//Find the best pipeline for each flavor
ImageProviderPipeline pipeline = newImageConverterPipeline(imageInfo, flavor);
if (pipeline == null) {
continue; //No suitable pipeline found for flavor
}
Penalty p = pipeline.getConversionPenalty(this.manager.getRegistry());
if (!p.isInfinitePenalty()) {
candidates.add(pipeline);
}
}
return (ImageProviderPipeline[])candidates.toArray(
new ImageProviderPipeline[candidates.size()]);
}
Finds and returns an array of ImageProviderPipeline
instances which can handle the convert the given Image
and return one of the given ImageFlavor
s. Params: - sourceImage – the image to be converted
- flavors – the possible target flavors
Returns: an array of pipelines
/**
* 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) {
List candidates = new java.util.ArrayList();
for (ImageFlavor flavor : flavors) {
//Find the best pipeline for each flavor
ImageProviderPipeline pipeline = newImageConverterPipeline(sourceImage, flavor);
if (pipeline != null) {
candidates.add(pipeline);
}
}
return (ImageProviderPipeline[])candidates.toArray(
new ImageProviderPipeline[candidates.size()]);
}
}