Java tutorial
/* * 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: ImageImplRegistry.java 924666 2010-03-18 08:26:30Z jeremias $ */ 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.image.loader.util.Penalty; 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); /** Infinite penalty value which shall force any implementation to become ineligible. */ public static final int INFINITE_PENALTY = Integer.MAX_VALUE; /** 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; /** A Map (key: implementation classes) with additional penalties to fine-tune the registry. */ private Map additionalPenalties = new java.util.HashMap(); //<String, Penalty> //Note: String as key chosen to avoid possible class-unloading leaks /** Singleton instance */ private static ImageImplRegistry defaultInstance; /** * Main constructor. This constructor allows to disable plug-in discovery for testing purposes. * @param discover true if implementation classes shall automatically be discovered. */ public ImageImplRegistry(boolean discover) { if (discover) { discoverClasspathImplementations(); } } /** * Main constructor. * @see #getDefaultInstance() */ public ImageImplRegistry() { this(true); } /** * 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); boolean MANUAL_REGISTRATION_OF_FOPCLASSES = true; while (iter.hasNext()) { registerPreloader((ImagePreloader) iter.next()); } if (MANUAL_REGISTRATION_OF_FOPCLASSES) { try { registerPreloader((ImagePreloader) Class.forName("org.apache.fop.image.loader.batik.PreloaderSVG") .newInstance()); } catch (IllegalAccessException ignore) { } catch (InstantiationException ignore) { } catch (ClassNotFoundException ignore) { } } //Dynamic registration of ImageLoaderFactories iter = Service.providers(ImageLoaderFactory.class, true); while (iter.hasNext()) { registerLoaderFactory((ImageLoaderFactory) iter.next()); } if (MANUAL_REGISTRATION_OF_FOPCLASSES) { try { registerLoaderFactory((ImageLoaderFactory) Class .forName("org.apache.fop.image.loader.batik.ImageLoaderFactorySVG").newInstance()); } catch (IllegalAccessException ignore) { } catch (InstantiationException ignore) { } catch (ClassNotFoundException ignore) { } } //Dynamic registration of ImageConverters iter = Service.providers(ImageConverter.class, true); while (iter.hasNext()) { registerConverter((ImageConverter) iter.next()); } if (MANUAL_REGISTRATION_OF_FOPCLASSES) { try { registerConverter((ImageConverter) Class .forName("org.apache.fop.image.loader.batik.ImageConverterSVG2G2D").newInstance()); } catch (IllegalAccessException ignore) { } catch (InstantiationException ignore) { } catch (ClassNotFoundException ignore) { } } } /** * 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; } /** Holder class for registered {@link ImagePreloader} instances. */ private static 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; long p1 = h1.preloader.getPriority(); p1 += getAdditionalPenalty(h1.preloader.getClass().getName()).getValue(); PreloaderHolder h2 = (PreloaderHolder) o2; int p2 = h2.preloader.getPriority(); p2 += getAdditionalPenalty(h2.preloader.getClass().getName()).getValue(); int diff = Penalty.truncate(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; } ImageLoader loader = factory.newImageLoader(flavor); int penalty = loader.getUsagePenalty(); 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(flavor)); Map flavorMap = (Map) loaders.get(mime); if (flavorMap != null) { Iterator flavorIter = flavorMap.keySet().iterator(); while (flavorIter.hasNext()) { ImageFlavor checkFlavor = (ImageFlavor) flavorIter.next(); if (checkFlavor.isCompatible(flavor)) { List factoryList = (List) flavorMap.get(checkFlavor); if (factoryList != null && factoryList.size() > 0) { Iterator factoryIter = factoryList.iterator(); while (factoryIter.hasNext()) { ImageLoaderFactory factory = (ImageLoaderFactory) factoryIter.next(); if (factory.isSupported(imageInfo)) { matches.add(factory); } } } } } } if (matches.size() == 0) { return null; } else { return (ImageLoaderFactory[]) matches.toArray(new ImageLoaderFactory[matches.size()]); } } /** Comparator for {@link ImageLoaderFactory} classes. */ private class ImageLoaderFactoryComparator implements Comparator { private ImageFlavor targetFlavor; public ImageLoaderFactoryComparator(ImageFlavor targetFlavor) { this.targetFlavor = targetFlavor; } public int compare(Object o1, Object o2) { ImageLoaderFactory f1 = (ImageLoaderFactory) o1; ImageLoader l1 = f1.newImageLoader(targetFlavor); long p1 = l1.getUsagePenalty(); p1 += getAdditionalPenalty(l1.getClass().getName()).getValue(); ImageLoaderFactory f2 = (ImageLoaderFactory) o2; ImageLoader l2 = f2.newImageLoader(targetFlavor); long p2 = l2.getUsagePenalty(); p2 = getAdditionalPenalty(l2.getClass().getName()).getValue(); //Lowest penalty first return Penalty.truncate(p1 - p2); } } /** * 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; } /** * Sets an additional penalty for a particular implementation class for any of the interface * administered by this registry class. No checking is performed to verify if the className * parameter is valid. * @param className the fully qualified class name of the implementation class * @param penalty the additional penalty or null to clear any existing value */ public void setAdditionalPenalty(String className, Penalty penalty) { if (penalty != null) { this.additionalPenalties.put(className, penalty); } else { this.additionalPenalties.remove(className); } this.lastPreloaderSort = -1; //Force resort, just in case this was a preloader } /** * Returns the additional penalty value set for a particular implementation class. * If no such value is set, 0 is returned. * @param className the fully qualified class name of the implementation class * @return the additional penalty value */ public Penalty getAdditionalPenalty(String className) { Penalty p = (Penalty) this.additionalPenalties.get(className); return (p != null ? p : Penalty.ZERO_PENALTY); } }