/*
 * 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 ImageFlavors.
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 ImageFlavors.
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()]); } }