/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.factory; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import org.geotools.resources.LazySet; import org.junit.*; import static org.junit.Assert.*; /** * Tests {@link FactoryRegistry} implementation. * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public final class FactoryRegistryTest { /** * Ensures that class {@link Hints} is loaded before {@link DummyFactory}. * It is not needed for normal execution, but Maven seems to mess with class loaders. */ @Before public void ensureHintsLoaded() { assertNotNull(Hints.DATUM_FACTORY.toString()); } /** * Creates the factory registry to test. The tests performed in this method are more * J2SE tests than Geotools implementation tests. We basically just ensure that we * have setup the service registry properly. * <p> * Factories are specified in arguments as {@link Factory} objects in order to avoid * the {@link DummyClass} to be initialized before {@link Hints}. This is not a problem * for normal execution, but Maven seems to mess with class loaders. * * @param creator {@code true} if the registry should be an instance of {@link FactoryCreator}. */ private FactoryRegistry getRegistry(final boolean creator, final Factory factory1, final Factory factory2, final Factory factory3) { @SuppressWarnings("unchecked") final Set<Class<?>> categories = (Set) Collections.singleton(DummyFactory.class); // The above line fails without the cast, I don't know why... final FactoryRegistry registry; if (creator) { registry = new FactoryCreator(categories); } else { registry = new FactoryRegistry(categories); } registry.registerServiceProvider(factory1); registry.registerServiceProvider(factory2); registry.registerServiceProvider(factory3); assertTrue(registry.setOrdering(DummyFactory.class, (DummyFactory)factory1, (DummyFactory)factory2)); assertTrue(registry.setOrdering(DummyFactory.class, (DummyFactory)factory2, (DummyFactory)factory3)); assertTrue(registry.setOrdering(DummyFactory.class, (DummyFactory)factory1, (DummyFactory)factory3)); final List<?> factories = new ArrayList<Object>(new LazySet<Object>( registry.getServiceProviders(DummyFactory.class, null, null))); assertTrue(factories.contains(factory1)); assertTrue(factories.contains(factory2)); assertTrue(factories.contains(factory3)); assertTrue(factories.indexOf(factory1) < factories.indexOf(factory2)); assertTrue(factories.indexOf(factory2) < factories.indexOf(factory3)); return registry; } /** * Tests the {@link FactoryRegistry#getProvider} method. * Note that the tested method do not create any new factory. * If no registered factory matching the hints is found, an exception is expected. * <br><br> * Three factories are initially registered: factory #1, #2 and #3. * * Factory #1 has no dependency. * Factory #2 uses factory #1. * Factory #3 uses factory #2, which implies an indirect dependency to factory #1. * * Additionnaly, factory #1 uses a KEY_INTERPOLATION hint. */ @Test public void testGetProvider() { final Hints.Key key = DummyFactory.DUMMY_FACTORY; final DummyFactory factory1 = new DummyFactory.Example1(); final DummyFactory factory2 = new DummyFactory.Example2(); final DummyFactory factory3 = new DummyFactory.Example3(); final FactoryRegistry registry = getRegistry(false, factory1, factory2, factory3); Hints hints; DummyFactory factory; // ------------------------------------------------ // PART 1: SIMPLE HINT (not a Factory hint) // ------------------------------------------------ /* * No hints. The fist factory should be selected. */ hints = null; factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("No preferences; should select the first factory. ", factory1, factory); /* * A hint compatible with one of our factories. Factory #1 declares explicitly that it uses * a bilinear interpolation, which is compatible with user's hints. All other factories are * indifferent. Since factory #1 is the first one in the list, it should be selected. */ hints = new Hints(Hints.KEY_INTERPOLATION, Hints.VALUE_INTERPOLATION_BILINEAR); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("First factory matches; it should be selected. ", factory1, factory); /* * A hint incompatible with all our factories. Factory #1 is the only one to defines * explicitly a KEY_INTERPOLATION hint, but all other factories depend on factory #1 * either directly (factory #2) or indirectly (factory #3, which depends on #2). */ hints = new Hints(Hints.KEY_INTERPOLATION, Hints.VALUE_INTERPOLATION_BICUBIC); try { factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); fail("Found factory "+factory+", while the hint should have been rejected."); } catch (FactoryNotFoundException exception) { // This is the expected exception. Continue... } /* * Add a new factory implementation, and try again with exactly the same hints * than the previous test. This time, the new factory should be selected since * this one doesn't have any dependency toward factory #1. */ final DummyFactory factory4 = new DummyFactory.Example4(); registry.registerServiceProvider(factory4); assertTrue(registry.setOrdering(DummyFactory.class, factory1, factory4)); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("The new factory should be selected. ", factory4, factory); // ---------------------------- // PART 2: FACTORY HINT // ---------------------------- /* * Trivial case: user gives explicitly a factory instance. */ DummyFactory explicit = new DummyFactory.Example3(); hints = new Hints(DummyFactory.DUMMY_FACTORY, explicit); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("The user-specified factory should have been selected. ", explicit, factory); /* * User specifies the expected implementation class rather than an instance. */ hints = new Hints(DummyFactory.DUMMY_FACTORY, DummyFactory.Example2.class); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("Factory of class #2 were requested. ", factory2, factory); /* * Same as above, but with classes specified in an array. */ hints = new Hints(DummyFactory.DUMMY_FACTORY, new Class<?>[] { DummyFactory.Example3.class, DummyFactory.Example2.class }); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("Factory of class #3 were requested. ", factory3, factory); /* * The following hint should be ignored by factory #1, since this factory doesn't have * any dependency to the INTERNAL_FACTORY hint. Since factory #1 is first in the ordering, * it should be selected. */ hints = new Hints(DummyFactory.INTERNAL_FACTORY, DummyFactory.Example2.class); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("Expected factory #1. ", factory1, factory); /* * If the user really wants some factory that do have a dependency to factory #2, he should * specifies in a DUMMY_FACTORY hint the implementation classes (or a common super-class or * interface) that do care about the INTERNAL_FACTORY hint. Note that this extra step should * not be a big deal in most real application, because: * * 1) Either all implementations have this dependency (for example it would be * unusual to see a DatumAuthorityFactory without a DatumFactory dependency); * * 2) or the user really know the implementation he wants (for example if he specifies a * JTS CoordinateSequenceFactory, he probably wants to use the JTS GeometryFactory). * * In the particular case of this test suite, this extra step would not be needed * neither if factory #1 was last in the ordering rather than first. */ final Hints implementations = new Hints(DummyFactory.DUMMY_FACTORY, new Class[] {DummyFactory.Example2.class, DummyFactory.Example3.class}); /* * Now search NOT for factory #1, but rather for a factory using #1 internally. * This is the case of factory #2. */ hints = new Hints(DummyFactory.INTERNAL_FACTORY, DummyFactory.Example1.class); hints.add(implementations); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("Expected a factory using #1 internally. ", factory2, factory); } /** * Tests the {@link FactoryCreator#getProvider} method. * This test tries again the cases that was expected to throws an exception in * {@link #testGetProvider}. But now, those cases are expected to creates automatically * new factory instances instead of throwing an exception. */ @Test public void testCreateProvider() { final Hints.Key key = DummyFactory.DUMMY_FACTORY; final DummyFactory factory1 = new DummyFactory.Example1(); final DummyFactory factory2 = new DummyFactory.Example2(); final DummyFactory factory3 = new DummyFactory.Example3(); final FactoryRegistry registry = getRegistry(true, factory1, factory2, factory3); Hints hints; DummyFactory factory; /* * Same tests than above (at least some of them). * See comments in 'testGetProvider()' for explanation. */ hints = new Hints(Hints.KEY_INTERPOLATION, Hints.VALUE_INTERPOLATION_BILINEAR); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("First factory matches; it should be selected. ", factory1, factory); hints = new Hints(DummyFactory.DUMMY_FACTORY, DummyFactory.Example2.class); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame("Factory of class #2 were requested. ", factory2, factory); /* * The following case was throwing an exception in testGetProvider(). It should fails again * here, but for a different reason. FactoryCreator is unable to creates automatically a new * factory instance, since we gave no implementation hint and no registered factory have a * constructor expecting a Hints argument. */ hints = new Hints(Hints.KEY_INTERPOLATION, Hints.VALUE_INTERPOLATION_BICUBIC); try { factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); fail("Found or created factory "+factory+", while it should not have been allowed."); } catch (FactoryNotFoundException exception) { // This is the expected exception. Continue... } /* * Register a DummyFactory with a constructor expecting a Hints argument, and try again * with the same hints. Now it should creates a new factory instance, because we are using * FactoryCreator instead of FactoryRegistry and an appropriate constructor is found. * Note that an AssertionFailedError should be thrown if the no-argument constructor of * Example5 is invoked, since the constructor with a Hints argument should have priority. */ final DummyFactory factory5 = new DummyFactory.Example5(null); registry.registerServiceProvider(factory5); assertTrue(registry.setOrdering(DummyFactory.class, factory1, factory5)); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertSame ("An instance of Factory #5 should have been created.", factory5.getClass(), factory.getClass()); assertNotSame("A NEW instance of Factory #5 should have been created", factory5, factory); /* * Tries again with a class explicitly specified as an implementation hint. * It doesn't matter if this class is registered or not. */ hints.put(DummyFactory.DUMMY_FACTORY, DummyFactory.Example4.class); factory = registry.getServiceProvider(DummyFactory.class, null, hints, key); assertEquals("An instance of Factory #4 should have been created.", DummyFactory.Example4.class, factory.getClass()); } @Test public void testLookupWithExtendedClasspath() { URL url = getClass().getResource("foo.jar"); assertNotNull(url); FactoryRegistry reg = new FactoryCreator( DummyInterface.class ); Iterator it = reg.getServiceProviders(DummyInterface.class, false); assertFalse(it.hasNext()); URLClassLoader cl = new URLClassLoader(new URL[]{url}); GeoTools.addClassLoader(cl); reg.scanForPlugins(); it = reg.getServiceProviders(DummyInterface.class, false); assertTrue(it.hasNext()); Object next = it.next(); assertEquals( "pkg.Foo", next.getClass().getName() ); } }