/* * 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. */ /** * @author Rustem V. Rafikov */ package com.google.code.appengine.imageio.spi; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.Stack; import java.util.Map.Entry; import org.apache.harmony.x.imageio.internal.nls.Messages; public class ServiceRegistry { CategoriesMap categories = new CategoriesMap(this); public ServiceRegistry(Iterator<Class<?>> categoriesIterator) { if (null == categoriesIterator) { throw new IllegalArgumentException(Messages.getString("imageio.5D")); } while(categoriesIterator.hasNext()) { Class<?> c = categoriesIterator.next(); categories.addCategory(c); } } public static <T> Iterator<T> lookupProviders(Class<T> providerClass, ClassLoader loader) { return new LookupProvidersIterator(providerClass, loader); } public static <T> Iterator<T> lookupProviders(Class<T> providerClass) { return lookupProviders(providerClass, Thread.currentThread().getContextClassLoader()); } public <T> boolean registerServiceProvider(T provider, Class<T> category) { return categories.addProvider(provider, category); } public void registerServiceProviders(Iterator<?> providers) { for (Iterator<?> iterator = providers; iterator.hasNext();) { categories.addProvider(iterator.next(), null); } } public void registerServiceProvider(Object provider) { categories.addProvider(provider, null); } public <T> boolean deregisterServiceProvider(T provider, Class<T> category) { return categories.removeProvider(provider, category); } public void deregisterServiceProvider(Object provider) { categories.removeProvider(provider); } // @SuppressWarnings("unchecked") public <T> Iterator<T> getServiceProviders(Class<T> category, Filter filter, boolean useOrdering) { return new FilteredIterator<T>(filter, (Iterator<T>) categories.getProviders(category, useOrdering)); } // @SuppressWarnings("unchecked") public <T> Iterator<T> getServiceProviders(Class<T> category, boolean useOrdering) { return (Iterator<T>) categories.getProviders(category, useOrdering); } public <T> T getServiceProviderByClass(Class<T> providerClass) { return categories.getServiceProviderByClass(providerClass); } public <T> boolean setOrdering(Class<T> category, T firstProvider, T secondProvider) { return categories.setOrdering(category, firstProvider, secondProvider); } public <T> boolean unsetOrdering(Class<T> category, T firstProvider, T secondProvider) { return categories.unsetOrdering(category, firstProvider, secondProvider); } public void deregisterAll(Class<?> category) { categories.removeAll(category); } public void deregisterAll() { categories.removeAll(); } @Override public void finalize() throws Throwable { deregisterAll(); } public boolean contains(Object provider) { if (provider == null) { throw new IllegalArgumentException(Messages.getString("imageio.5E")); } return categories.contains(provider); } public Iterator<Class<?>> getCategories() { return categories.list(); } public static interface Filter { boolean filter(Object provider); } private static class CategoriesMap { Map<Class<?>, ProvidersMap> categories = new HashMap<Class<?>, ProvidersMap>(); ServiceRegistry registry; public CategoriesMap(ServiceRegistry registry) { this.registry = registry; } boolean contains(Object provider) { for (Map.Entry<Class<?>, ProvidersMap> e : categories.entrySet()) { ProvidersMap providers = e.getValue(); if (providers.contains(provider)) { return true; } } return false; } <T> boolean setOrdering(Class<T> category, T firstProvider, T secondProvider) { ProvidersMap providers = categories.get(category); if (providers == null) { throw new IllegalArgumentException(Messages.getString("imageio.92", category)); } return providers.setOrdering(firstProvider, secondProvider); } <T> boolean unsetOrdering(Class<T> category, T firstProvider, T secondProvider) { ProvidersMap providers = categories.get(category); if (providers == null) { throw new IllegalArgumentException(Messages.getString("imageio.92", category)); } return providers.unsetOrdering(firstProvider, secondProvider); } Iterator<?> getProviders(Class<?> category, boolean useOrdering) { ProvidersMap providers = categories.get(category); if (null == providers) { throw new IllegalArgumentException(Messages.getString("imageio.92", category)); } return providers.getProviders(useOrdering); } <T> T getServiceProviderByClass(Class<T> providerClass) { for (Map.Entry<Class<?>, ProvidersMap> e : categories.entrySet()) { if (e.getKey().isAssignableFrom(providerClass)) { T provider = e.getValue().getServiceProviderByClass(providerClass); if (provider != null) { return provider; } } } return null; } Iterator<Class<?>> list() { return categories.keySet().iterator(); } void addCategory(Class<?> category) { categories.put(category, new ProvidersMap()); } /** * Adds a provider to the category. If <code>category</code> is * <code>null</code> then the provider will be added to all categories * which the provider is assignable from. * @param provider provider to add * @param category category to add provider to * @return if there were such provider in some category */ boolean addProvider(Object provider, Class<?> category) { if (provider == null) { throw new IllegalArgumentException(Messages.getString("imageio.5E")); } boolean rt; if (category == null) { rt = findAndAdd(provider); } else { rt = addToNamed(provider, category); } if (provider instanceof RegisterableService) { ((RegisterableService) provider).onRegistration(registry, category); } return rt; } private boolean addToNamed(Object provider, Class<?> category) { if (!category.isAssignableFrom(provider.getClass())) { throw new ClassCastException(); } Object obj = categories.get(category); if (null == obj) { throw new IllegalArgumentException(Messages.getString("imageio.92", category)); } return ((ProvidersMap) obj).addProvider(provider); } private boolean findAndAdd(Object provider) { boolean rt = false; for (Entry<Class<?>, ProvidersMap> e : categories.entrySet()) { if (e.getKey().isAssignableFrom(provider.getClass())) { rt |= e.getValue().addProvider(provider); } } return rt; } boolean removeProvider(Object provider, Class<?> category) { if (provider == null) { throw new IllegalArgumentException(Messages.getString("imageio.5E")); } if (!category.isAssignableFrom(provider.getClass())) { throw new ClassCastException(); } Object obj = categories.get(category); if (obj == null) { throw new IllegalArgumentException(Messages.getString("imageio.92", category)); } return ((ProvidersMap) obj).removeProvider(provider, registry, category); } void removeProvider(Object provider) { if (provider == null) { throw new IllegalArgumentException(Messages.getString("imageio.5E")); } for (Entry<Class<?>, ProvidersMap> e : categories.entrySet()) { ProvidersMap providers = e.getValue(); providers.removeProvider(provider, registry, e.getKey()); } } void removeAll(Class<?> category) { Object obj = categories.get(category); if (obj == null) { throw new IllegalArgumentException(Messages.getString("imageio.92", category)); } ((ProvidersMap) obj).clear(registry); } void removeAll() { for ( Map.Entry<Class<?>, ProvidersMap> e : categories.entrySet()) { removeAll(e.getKey()); } } } private static class ProvidersMap { Map<Class<?>, Object> providers = new HashMap<Class<?>, Object>(); Map<Object, ProviderNode> nodeMap = new HashMap<Object, ProviderNode>(); boolean addProvider(Object provider) { ProviderNode node = new ProviderNode(provider); nodeMap.put(provider, node); Object obj = providers.put(provider.getClass(), provider); if (obj != null) { nodeMap.remove(obj); return false; } return true; } boolean contains(Object provider) { return providers.containsValue(provider); } boolean removeProvider(Object provider, ServiceRegistry registry, Class<?> category) { Object obj = providers.get(provider.getClass()); if ((obj == null) || (obj != provider)) { return false; } providers.remove(provider.getClass()); nodeMap.remove(provider); if (provider instanceof RegisterableService) { ((RegisterableService) provider).onDeregistration(registry, category); } return true; } void clear(ServiceRegistry registry) { for (Map.Entry<Class<?>, Object> e : providers.entrySet()) { Object provider = e.getValue(); if (provider instanceof RegisterableService) { ((RegisterableService) provider).onDeregistration(registry, e.getKey()); } } providers.clear(); nodeMap.clear(); } Iterator<Class<?>> getProviderClasses() { return providers.keySet().iterator(); } Iterator<?> getProviders(boolean useOrdering) { if (useOrdering) { return new OrderedProviderIterator(nodeMap.values().iterator()); } return providers.values().iterator(); } @SuppressWarnings("unchecked") <T> T getServiceProviderByClass(Class<T> providerClass) { return (T)providers.get(providerClass); } public <T> boolean setOrdering(T firstProvider, T secondProvider) { if (firstProvider == secondProvider) { throw new IllegalArgumentException(Messages.getString("imageio.98")); } if ((firstProvider == null) || (secondProvider == null)) { throw new IllegalArgumentException(Messages.getString("imageio.5E")); } ProviderNode firstNode = nodeMap.get(firstProvider); ProviderNode secondNode = nodeMap.get(secondProvider); // if the ordering is already set, return false if ((firstNode == null) || (firstNode.contains(secondNode))) { return false; } // put secondProvider into firstProvider's outgoing nodes list firstNode.addOutEdge(secondNode); // increase secondNode's incoming edge by 1 secondNode.addInEdge(); return true; } public <T> boolean unsetOrdering(T firstProvider, T secondProvider) { if (firstProvider == secondProvider) { throw new IllegalArgumentException(Messages.getString("imageio.98")); } if ((firstProvider == null) || (secondProvider == null)) { throw new IllegalArgumentException(Messages.getString("imageio.5E")); } ProviderNode firstNode = nodeMap.get(firstProvider); ProviderNode secondNode = nodeMap.get(secondProvider); // if the ordering is not set, return false if ((firstNode == null) || (!firstNode.contains(secondNode))) { return false; } // remove secondProvider from firstProvider's outgoing nodes list firstNode.removeOutEdge(secondNode); // decrease secondNode's incoming edge by 1 secondNode.removeInEdge(); return true; } } private static class FilteredIterator<E> implements Iterator<E> { private Filter filter; private Iterator<E> backend; private E nextObj; public FilteredIterator(Filter filter, Iterator<E> backend) { this.filter = filter; this.backend = backend; findNext(); } public E next() { if (nextObj == null) { throw new NoSuchElementException(); } E tmp = nextObj; findNext(); return tmp; } public boolean hasNext() { return nextObj != null; } public void remove() { throw new UnsupportedOperationException(); } /** * Sets nextObj to a next provider matching the criterion given by the filter */ private void findNext() { nextObj = null; while (backend.hasNext()) { E o = backend.next(); if (filter.filter(o)) { nextObj = o; return; } } } } private static class ProviderNode { // number of incoming edges private int incomingEdges; // all outgoing nodes private Set<Object> outgoingNodes; private Object provider; public ProviderNode(Object provider) { incomingEdges = 0; outgoingNodes = new HashSet<Object>(); this.provider = provider; } public Object getProvider() { return provider; } public Iterator<Object> getOutgoingNodes() { return outgoingNodes.iterator(); } public boolean addOutEdge(Object secondProvider) { return outgoingNodes.add(secondProvider); } public <T> boolean removeOutEdge(Object provider) { return outgoingNodes.remove(provider); } public void addInEdge() { incomingEdges++; } public void removeInEdge() { incomingEdges--; } public int getIncomingEdges() { return incomingEdges; } public boolean contains(Object provider) { return outgoingNodes.contains(provider); } } /** * The iterator implements Kahn topological sorting algorithm. * @see <a href="http://en.wikipedia.org/wiki/Topological_sorting">Wikipedia</a> * for further reference. */ private static class OrderedProviderIterator implements Iterator { // the stack contains nodes which has no lesser nodes // except those already returned by the iterator private Stack<ProviderNode> firstNodes = new Stack<ProviderNode>(); // a dynamic counter of incoming nodes // when a node is returned by iterator, the counters for connected // nodes decrement private Map<ProviderNode, Integer> incomingEdges = new HashMap<ProviderNode, Integer>(); public OrderedProviderIterator(Iterator it) { // find all the nodes that with no incoming edges and // add them to firstNodes while (it.hasNext()) { ProviderNode node = (ProviderNode) it.next(); incomingEdges.put(node, new Integer(node.getIncomingEdges())); if (node.getIncomingEdges() == 0) { firstNodes.push(node); } } } public boolean hasNext() { return !firstNodes.empty(); } public Object next() { if (firstNodes.empty()) { throw new NoSuchElementException(); } // get a node from firstNodes ProviderNode node = firstNodes.pop(); // find all the outgoing nodes Iterator it = node.getOutgoingNodes(); while (it.hasNext()) { ProviderNode outNode = (ProviderNode) it.next(); // remove the incoming edge from the node. int edges = incomingEdges.get(outNode); edges--; incomingEdges.put(outNode, new Integer(edges)); // add to the firstNodes if this node's incoming edge is equal to 0 if (edges == 0) { firstNodes.push(outNode); } } incomingEdges.remove(node); return node.getProvider(); } public void remove() { throw new UnsupportedOperationException(); } } static class LookupProvidersIterator<T> implements Iterator { private Set<String> providerNames = new HashSet<String>(); private Iterator<String> it = null; private ClassLoader loader = null; public LookupProvidersIterator(Class<T> providerClass, ClassLoader loader) { this.loader = loader; Enumeration<URL> e = null; try { e = loader.getResources("META-INF/services/"+providerClass.getName()); //$NON-NLS-1$ while (e.hasMoreElements()) { Set<String> names = parse((URL)e.nextElement()); providerNames.addAll(names); } } catch (IOException e1) { // Ignored } it = providerNames.iterator(); } /* * Parse the provider-configuration file as specified * @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Provider Configuration File">JAR File Specification</a> */ private Set<String> parse(URL u) { InputStream input = null; BufferedReader reader = null; Set<String> names = new HashSet<String>(); try { input = u.openStream(); reader = new BufferedReader(new InputStreamReader(input, "utf-8")); //$NON-NLS-1$ String line; while ((line = reader.readLine()) != null) { // The comment character is '#' (0x23) // on each line all characters following the first comment character are ignored int sharpIndex = line.indexOf('#'); if (sharpIndex>=0) { line = line.substring(0, sharpIndex); } // Whitespaces are ignored line = line.trim(); if (line.length()>0) { // a java class name, check if identifier correct char[] namechars = line.toCharArray(); for (int i = 0; i < namechars.length; i++) { if (!(Character.isJavaIdentifierPart(namechars[i]) || namechars[i] == '.')) { throw new ServiceConfigurationError(Messages.getString("imageio.99", line)); } } names.add(line); } } } catch (IOException e) { throw new ServiceConfigurationError(e.toString()); } finally { try { if (reader != null) { reader.close(); } if (input != null) { input.close(); } } catch (IOException e) { throw new ServiceConfigurationError(e.toString()); } } return names; } public boolean hasNext() { return it.hasNext(); } public Object next() { if (!hasNext()) { throw new NoSuchElementException(); } String name = (String)it.next(); try { return Class.forName(name, true, loader).newInstance(); } catch (Exception e) { throw new ServiceConfigurationError(e.toString()); } } public void remove() { throw new UnsupportedOperationException(); } } /** * An Error that can be thrown when something wrong occurs in loading a service * provider. */ static class ServiceConfigurationError extends Error { private static final long serialVersionUID = 74132770414881L; /** * The constructor * * @param msg * the message of this error */ public ServiceConfigurationError(String msg) { super(msg); } /** * The constructor * * @param msg * the message of this error * @param cause * the cause of this error */ public ServiceConfigurationError(String msg, Throwable cause) { super(msg, cause); } } }