blob: 780ad68675d2685ce6a429a09ed2611474241be3 [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.spi;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.util.Service;
/**
* This class is the registry for all implementations of the various service provider interfaces
* for the image package.
*/
public class ImageImplRegistry {
/** logger */
protected static Log log = LogFactory.getLog(ImageImplRegistry.class);
/** Holds the list of preloaders */
private List preloaders = new java.util.ArrayList();
//Content: List<ImagePreloader>
private int lastPreloaderIdentifier;
private int lastPreloaderSort;
/** Holds the list of ImageLoaderFactories */
private Map loaders = new java.util.HashMap();
//Content: Map<String,Map<ImageFlavor,ImageLoaderFactory>>
/** Holds the list of ImageConverters */
private List converters = new java.util.ArrayList();
//Content: List<ImageConverter>
private int converterModifications;
/** Singleton instance */
private static ImageImplRegistry defaultInstance;
/**
* Main constructor.
* @see #getDefaultInstance()
*/
public ImageImplRegistry() {
discoverClasspathImplementations();
}
/**
* Returns the default instance of the Image implementation registry.
* @return the default instance
*/
public static ImageImplRegistry getDefaultInstance() {
if (defaultInstance == null) {
defaultInstance = new ImageImplRegistry();
}
return defaultInstance;
}
/**
* Discovers all implementations in the application's classpath.
*/
public void discoverClasspathImplementations() {
//Dynamic registration of ImagePreloaders
Iterator iter = Service.providers(ImagePreloader.class, true);
while (iter.hasNext()) {
registerPreloader((ImagePreloader)iter.next());
}
//Dynamic registration of ImageLoaderFactories
iter = Service.providers(ImageLoaderFactory.class, true);
while (iter.hasNext()) {
registerLoaderFactory((ImageLoaderFactory)iter.next());
}
//Dynamic registration of ImageConverters
iter = Service.providers(ImageConverter.class, true);
while (iter.hasNext()) {
registerConverter((ImageConverter)iter.next());
}
}
/**
* Registers a new ImagePreloader.
* @param preloader An ImagePreloader instance
*/
public void registerPreloader(ImagePreloader preloader) {
if (log.isDebugEnabled()) {
log.debug("Registered " + preloader.getClass().getName()
+ " with priority " + preloader.getPriority());
}
preloaders.add(newPreloaderHolder(preloader));
}
private synchronized PreloaderHolder newPreloaderHolder(ImagePreloader preloader) {
PreloaderHolder holder = new PreloaderHolder();
holder.preloader = preloader;
holder.identifier = ++lastPreloaderIdentifier;
return holder;
}
private class PreloaderHolder {
private ImagePreloader preloader;
private int identifier;
public String toString() {
return preloader + " " + identifier;
}
}
private synchronized void sortPreloaders() {
if (this.lastPreloaderIdentifier != this.lastPreloaderSort) {
Collections.sort(this.preloaders, new Comparator() {
public int compare(Object o1, Object o2) {
PreloaderHolder h1 = (PreloaderHolder)o1;
PreloaderHolder h2 = (PreloaderHolder)o2;
int p1 = h1.preloader.getPriority();
int p2 = h2.preloader.getPriority();
int diff = p1 - p2;
if (diff != 0) {
return diff;
} else {
diff = h1.identifier - h2.identifier;
return diff;
}
}
});
this.lastPreloaderSort = lastPreloaderIdentifier;
}
}
/**
* Registers a new ImageLoaderFactory.
* @param loaderFactory An ImageLoaderFactory instance
*/
public void registerLoaderFactory(ImageLoaderFactory loaderFactory) {
if (!loaderFactory.isAvailable()) {
if (log.isDebugEnabled()) {
log.debug("ImageLoaderFactory reports not available: "
+ loaderFactory.getClass().getName());
}
return;
}
String[] mimes = loaderFactory.getSupportedMIMETypes();
for (int i = 0, ci = mimes.length; i < ci; i++) {
String mime = mimes[i];
synchronized (loaders) {
Map flavorMap = (Map)loaders.get(mime);
if (flavorMap == null) {
flavorMap = new java.util.HashMap();
loaders.put(mime, flavorMap);
}
ImageFlavor[] flavors = loaderFactory.getSupportedFlavors(mime);
for (int j = 0, cj = flavors.length; j < cj; j++) {
ImageFlavor flavor = flavors[j];
List factoryList = (List)flavorMap.get(flavor);
if (factoryList == null) {
factoryList = new java.util.ArrayList();
flavorMap.put(flavor, factoryList);
}
factoryList.add(loaderFactory);
if (log.isDebugEnabled()) {
log.debug("Registered " + loaderFactory.getClass().getName()
+ ": MIME = " + mime + ", Flavor = " + flavor);
}
}
}
}
}
/**
* Returns the Collection of registered ImageConverter instances.
* @return a Collection<ImageConverter>
*/
public Collection getImageConverters() {
return Collections.unmodifiableList(this.converters);
}
/**
* Returns the number of modifications to the collection of registered ImageConverter instances.
* This is used to detect changes in the registry concerning ImageConverters.
* @return the number of modifications
*/
public int getImageConverterModifications() {
return this.converterModifications;
}
/**
* Registers a new ImageConverter.
* @param converter An ImageConverter instance
*/
public void registerConverter(ImageConverter converter) {
converters.add(converter);
converterModifications++;
if (log.isDebugEnabled()) {
log.debug("Registered: " + converter.getClass().getName());
}
}
/**
* Returns an iterator over all registered ImagePreloader instances.
* @return an iterator over ImagePreloader instances.
*/
public Iterator getPreloaderIterator() {
sortPreloaders();
final Iterator iter = this.preloaders.iterator();
//Unpack the holders
return new Iterator() {
public boolean hasNext() {
return iter.hasNext();
}
public Object next() {
Object obj = iter.next();
if (obj != null) {
return ((PreloaderHolder)obj).preloader;
} else {
return null;
}
}
public void remove() {
iter.remove();
}
};
}
/**
* Returns the best ImageLoaderFactory supporting the {@link ImageInfo} and image flavor.
* If there are multiple ImageLoaderFactories the one with the least usage penalty is selected.
* @param imageInfo the image info object
* @param flavor the image flavor.
* @return an ImageLoaderFactory instance or null, if no suitable implementation was found
*/
public ImageLoaderFactory getImageLoaderFactory(ImageInfo imageInfo, ImageFlavor flavor) {
String mime = imageInfo.getMimeType();
Map flavorMap = (Map)loaders.get(mime);
if (flavorMap != null) {
List factoryList = (List)flavorMap.get(flavor);
if (factoryList != null && factoryList.size() > 0) {
Iterator iter = factoryList.iterator();
int bestPenalty = Integer.MAX_VALUE;
ImageLoaderFactory bestFactory = null;
while (iter.hasNext()) {
ImageLoaderFactory factory = (ImageLoaderFactory)iter.next();
if (!factory.isSupported(imageInfo)) {
continue;
}
int penalty = factory.getUsagePenalty(mime, flavor);
if (penalty < bestPenalty) {
bestPenalty = penalty;
bestFactory = factory;
}
}
return bestFactory;
}
}
return null;
}
/**
* Returns an array of {@link ImageLoaderFactory} instances that support the MIME type
* indicated by an {@link ImageInfo} object and can generate the given image flavor.
* @param imageInfo the image info object
* @param flavor the target image flavor
* @return the array of image loader factories
*/
public ImageLoaderFactory[] getImageLoaderFactories(ImageInfo imageInfo, ImageFlavor flavor) {
String mime = imageInfo.getMimeType();
Collection matches = new java.util.TreeSet(new ImageLoaderFactoryComparator(mime, flavor));
Map flavorMap = (Map)loaders.get(mime);
if (flavorMap != null) {
List factoryList = (List)flavorMap.get(flavor);
if (factoryList != null && factoryList.size() > 0) {
Iterator iter = factoryList.iterator();
while (iter.hasNext()) {
ImageLoaderFactory factory = (ImageLoaderFactory)iter.next();
if (factory.isSupported(imageInfo)) {
matches.add(factory);
}
}
}
}
if (matches.size() == 0) {
return null;
} else {
return (ImageLoaderFactory[])matches.toArray(new ImageLoaderFactory[matches.size()]);
}
}
private static class ImageLoaderFactoryComparator implements Comparator {
private String mime;
private ImageFlavor targetFlavor;
public ImageLoaderFactoryComparator(String mime, ImageFlavor targetFlavor) {
this.mime = mime;
this.targetFlavor = targetFlavor;
}
public int compare(Object o1, Object o2) {
ImageLoaderFactory f1 = (ImageLoaderFactory)o1;
ImageLoaderFactory f2 = (ImageLoaderFactory)o2;
//Lowest penalty first
return f1.getUsagePenalty(mime, targetFlavor) - f2.getUsagePenalty(mime, targetFlavor);
}
}
/**
* Returns an array of ImageLoaderFactory instances which support the given MIME type. The
* instances are returned in no particular order.
* @param mime the MIME type to find ImageLoaderFactories for
* @return the array of ImageLoaderFactory instances
*/
public ImageLoaderFactory[] getImageLoaderFactories(String mime) {
Map flavorMap = (Map)loaders.get(mime);
if (flavorMap != null) {
Set factories = new java.util.HashSet();
Iterator iter = flavorMap.values().iterator();
while (iter.hasNext()) {
List factoryList = (List)iter.next();
factories.addAll(factoryList);
}
int factoryCount = factories.size();
if (factoryCount > 0) {
return (ImageLoaderFactory[])factories.toArray(
new ImageLoaderFactory[factoryCount]);
}
}
return null;
}
}