This is an automated email from the ASF dual-hosted git repository.

ppkarwasz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/commons-xml.git

commit 3c596a611a27f01c711c37655b049acebb60738f
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sat Apr 25 11:40:34 2026 +0200

    feat: remove ProviderRegistry abstraction
---
 .../apache/commons/xml/factory/XmlFactories.java   |  20 +--
 .../xml/factory/internal/CompositeProvider.java    | 143 ++++++++++++++++++++
 .../xml/factory/internal/ProviderRegistry.java     | 111 ----------------
 .../factory/UnsupportedXmlImplementationTest.java  |   4 +-
 .../attacks/SaxonXPathExternalCallsTest.java       |   6 +-
 .../factory/internal/CompositeProviderTest.java    | 146 +++++++++++++++++++++
 .../xml/factory/internal/ProviderRegistryTest.java | 101 --------------
 7 files changed, 300 insertions(+), 231 deletions(-)

diff --git a/src/main/java/org/apache/commons/xml/factory/XmlFactories.java 
b/src/main/java/org/apache/commons/xml/factory/XmlFactories.java
index 3733d0c..09b73bf 100644
--- a/src/main/java/org/apache/commons/xml/factory/XmlFactories.java
+++ b/src/main/java/org/apache/commons/xml/factory/XmlFactories.java
@@ -24,7 +24,7 @@
 import javax.xml.validation.SchemaFactory;
 import javax.xml.xpath.XPathFactory;
 
-import org.apache.commons.xml.factory.internal.ProviderRegistry;
+import org.apache.commons.xml.factory.internal.CompositeProvider;
 import org.apache.commons.xml.factory.spi.XmlProvider;
 
 /**
@@ -74,8 +74,7 @@ public final class XmlFactories {
      *         apply the hardening settings to it.
      */
     public static DocumentBuilderFactory newDocumentBuilderFactory() {
-        final DocumentBuilderFactory factory = 
DocumentBuilderFactory.newInstance();
-        return 
ProviderRegistry.getInstance().providerFor(factory.getClass()).configure(factory);
+        return 
CompositeProvider.getInstance().configure(DocumentBuilderFactory.newInstance());
     }
 
     /**
@@ -96,8 +95,7 @@ public static DocumentBuilderFactory 
newDocumentBuilderFactory() {
      *         apply the hardening settings to it.
      */
     public static SAXParserFactory newSAXParserFactory() {
-        final SAXParserFactory factory = SAXParserFactory.newInstance();
-        return 
ProviderRegistry.getInstance().providerFor(factory.getClass()).configure(factory);
+        return 
CompositeProvider.getInstance().configure(SAXParserFactory.newInstance());
     }
 
     /**
@@ -128,8 +126,7 @@ public static SAXParserFactory newSAXParserFactory() {
      *         apply the hardening settings to it.
      */
     public static SchemaFactory newSchemaFactory() {
-        final SchemaFactory factory = 
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-        return 
ProviderRegistry.getInstance().providerFor(factory.getClass()).configure(factory);
+        return 
CompositeProvider.getInstance().configure(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI));
     }
 
     /**
@@ -157,8 +154,7 @@ public static SchemaFactory newSchemaFactory() {
      *         apply the hardening settings to it.
      */
     public static TransformerFactory newTransformerFactory() {
-        final TransformerFactory factory = TransformerFactory.newInstance();
-        return 
ProviderRegistry.getInstance().providerFor(factory.getClass()).configure(factory);
+        return 
CompositeProvider.getInstance().configure(TransformerFactory.newInstance());
     }
 
     /**
@@ -184,8 +180,7 @@ public static TransformerFactory newTransformerFactory() {
      *         apply the hardening settings to it.
      */
     public static XMLInputFactory newXMLInputFactory() {
-        final XMLInputFactory factory = XMLInputFactory.newInstance();
-        return 
ProviderRegistry.getInstance().providerFor(factory.getClass()).configure(factory);
+        return 
CompositeProvider.getInstance().configure(XMLInputFactory.newInstance());
     }
 
     /**
@@ -205,8 +200,7 @@ public static XMLInputFactory newXMLInputFactory() {
      *         apply the hardening settings to it.
      */
     public static XPathFactory newXPathFactory() {
-        final XPathFactory factory = XPathFactory.newInstance();
-        return 
ProviderRegistry.getInstance().providerFor(factory.getClass()).configure(factory);
+        return 
CompositeProvider.getInstance().configure(XPathFactory.newInstance());
     }
 
     private XmlFactories() {
diff --git 
a/src/main/java/org/apache/commons/xml/factory/internal/CompositeProvider.java 
b/src/main/java/org/apache/commons/xml/factory/internal/CompositeProvider.java
new file mode 100644
index 0000000..f447f79
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/xml/factory/internal/CompositeProvider.java
@@ -0,0 +1,143 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+package org.apache.commons.xml.factory.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.commons.xml.factory.spi.XmlProvider;
+
+/**
+ * Internal {@link XmlProvider} that routes each {@code configure} call to the 
bundled or {@link ServiceLoader}-discovered provider responsible for the 
concrete
+ * JAXP factory class.
+ *
+ * <h2>Dispatch order</h2>
+ *
+ * <p>Providers are consulted in a single fixed list, built once when this 
class is loaded:</p>
+ *
+ * <ol>
+ *   <li>Bundled providers in declared order: {@link StockJdkProvider}, {@link 
XercesProvider}, {@link WoodstoxProvider}, {@link SaxonProvider}.</li>
+ *   <li>Third-party providers discovered via {@link ServiceLoader}, in the 
order {@link ServiceLoader} yields them.</li>
+ * </ol>
+ *
+ * <p>Bundled providers come first so that a third-party provider on the 
classpath cannot intercept hardening of a factory class this library already
+ * supports.</p>
+ *
+ * <p>If no provider matches, {@link HardeningException} is thrown.</p>
+ *
+ * <p>Although this class implements {@link XmlProvider}, its {@link 
#supports(Class)} method returns {@code false}: callers ask the composite to
+ * {@code configure}, never to advertise support. The interface implementation 
lets {@link org.apache.commons.xml.factory.XmlFactories} treat hardening
+ * uniformly without an extra abstraction.</p>
+ */
+public final class CompositeProvider implements XmlProvider {
+
+    private static final CompositeProvider INSTANCE = new 
CompositeProvider(defaultProviders());
+
+    private final List<XmlProvider> providers;
+
+    /**
+     * Constructs a composite over the supplied provider list.
+     *
+     * <p>Package-private for test use; production code should call {@link 
#getInstance()}.</p>
+     */
+    CompositeProvider(final List<XmlProvider> providers) {
+        this.providers = Collections.unmodifiableList(new 
ArrayList<>(providers));
+    }
+
+    /**
+     * Gets the process-wide composite instance.
+     *
+     * @return the singleton.
+     */
+    public static CompositeProvider getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Stub implementation: always returns {@code false}.
+     *
+     * <p>The {@link XmlProvider} contract requires this method, but the class 
is internal and nothing in the library invokes it.</p>
+     *
+     * @param factoryClass ignored.
+     * @return {@code false}.
+     */
+    @Override
+    public boolean supports(final Class<?> factoryClass) {
+        return false;
+    }
+
+    @Override
+    public DocumentBuilderFactory configure(final DocumentBuilderFactory 
factory) {
+        return providerFor(factory.getClass()).configure(factory);
+    }
+
+    @Override
+    public SAXParserFactory configure(final SAXParserFactory factory) {
+        return providerFor(factory.getClass()).configure(factory);
+    }
+
+    @Override
+    public XMLInputFactory configure(final XMLInputFactory factory) {
+        return providerFor(factory.getClass()).configure(factory);
+    }
+
+    @Override
+    public TransformerFactory configure(final TransformerFactory factory) {
+        return providerFor(factory.getClass()).configure(factory);
+    }
+
+    @Override
+    public XPathFactory configure(final XPathFactory factory) {
+        return providerFor(factory.getClass()).configure(factory);
+    }
+
+    @Override
+    public SchemaFactory configure(final SchemaFactory factory) {
+        return providerFor(factory.getClass()).configure(factory);
+    }
+
+    private XmlProvider providerFor(final Class<?> factoryClass) {
+        for (final XmlProvider provider : providers) {
+            if (provider.supports(factoryClass)) {
+                return provider;
+            }
+        }
+        throw new HardeningException(String.format(
+                "No XmlProvider supports JAXP factory class %s. Add a 
supported JAXP implementation to the classpath, "
+                        + "or register a custom XmlProvider via 
ServiceLoader.",
+                factoryClass.getName()));
+    }
+
+    private static List<XmlProvider> defaultProviders() {
+        final List<XmlProvider> all = new ArrayList<>();
+        all.add(new StockJdkProvider());
+        all.add(new XercesProvider());
+        all.add(new WoodstoxProvider());
+        all.add(new SaxonProvider());
+        ServiceLoader.load(XmlProvider.class, 
CompositeProvider.class.getClassLoader()).forEach(all::add);
+        return all;
+    }
+}
diff --git 
a/src/main/java/org/apache/commons/xml/factory/internal/ProviderRegistry.java 
b/src/main/java/org/apache/commons/xml/factory/internal/ProviderRegistry.java
deleted file mode 100644
index 5dad3b5..0000000
--- 
a/src/main/java/org/apache/commons/xml/factory/internal/ProviderRegistry.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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
- *
- *      https://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.
- */
-package org.apache.commons.xml.factory.internal;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.ServiceLoader;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.commons.xml.factory.spi.XmlProvider;
-
-/**
- * Internal lookup for the {@link XmlProvider} responsible for a given 
concrete JAXP factory class.
- *
- * <h2>Dispatch order</h2>
- *
- * <ol>
- *   <li>Bundled providers in a fixed order: {@link StockJdkProvider}, {@link 
XercesProvider}, {@link WoodstoxProvider}, {@link SaxonProvider}.</li>
- *   <li>Third-party providers discovered via {@link ServiceLoader}, in the 
order {@link ServiceLoader} yields them.</li>
- *   <li>If nothing matches, {@link HardeningException}.</li>
- * </ol>
- *
- * <p>Bundled providers are consulted first so that a third-party provider on 
the classpath cannot intercept hardening of a factory class this library already
- * supports.</p>
- *
- * <p>Lookup results are cached keyed by factory class. The cache is 
thread-safe and only holds {@link XmlProvider} instances, never the factories
- * themselves.</p>
- */
-public final class ProviderRegistry {
-
-    private static final ProviderRegistry INSTANCE = new 
ProviderRegistry(bundledProviders());
-
-    private final List<XmlProvider> bundled;
-
-    private final ConcurrentMap<Class<?>, XmlProvider> cache = new 
ConcurrentHashMap<>();
-
-    /**
-     * Constructs a registry with the supplied bundled providers.
-     *
-     * <p>Package-private for test use; production code should call {@link 
#getInstance()}.</p>
-     */
-    ProviderRegistry(final List<XmlProvider> bundled) {
-        this.bundled = Collections.unmodifiableList(new ArrayList<>(bundled));
-    }
-
-    /**
-     * Gets the process-wide registry instance.
-     *
-     * @return the singleton.
-     */
-    public static ProviderRegistry getInstance() {
-        return INSTANCE;
-    }
-
-    /**
-     * Looks up the provider responsible for factories of the supplied 
concrete class.
-     *
-     * @param factoryClass the concrete factory class; never {@code null}.
-     * @return the matching provider.
-     * @throws HardeningException if no provider matches.
-     */
-    public XmlProvider providerFor(final Class<?> factoryClass) {
-        Objects.requireNonNull(factoryClass);
-        return cache.computeIfAbsent(factoryClass, this::lookup);
-    }
-
-    private XmlProvider lookup(final Class<?> factoryClass) {
-        for (final XmlProvider provider : bundled) {
-            if (provider.supports(factoryClass)) {
-                return provider;
-            }
-        }
-        for (final XmlProvider provider : serviceLoaderProviders()) {
-            if (provider.supports(factoryClass)) {
-                return provider;
-            }
-        }
-        throw new HardeningException(String.format(
-                "No XmlProvider supports JAXP factory class %s. Add a 
supported JAXP implementation to the classpath, "
-                        + "or register a custom XmlProvider via 
ServiceLoader.",
-                factoryClass.getName()));
-    }
-
-    private static List<XmlProvider> bundledProviders() {
-        return Arrays.asList(new StockJdkProvider(), new XercesProvider(), new 
WoodstoxProvider(), new SaxonProvider());
-    }
-
-    private static Iterable<XmlProvider> serviceLoaderProviders() {
-        final ClassLoader loader = ProviderRegistry.class.getClassLoader();
-        return ServiceLoader.load(XmlProvider.class, loader);
-    }
-
-}
diff --git 
a/src/test/java/org/apache/commons/xml/factory/UnsupportedXmlImplementationTest.java
 
b/src/test/java/org/apache/commons/xml/factory/UnsupportedXmlImplementationTest.java
index a22b0c2..2eba600 100644
--- 
a/src/test/java/org/apache/commons/xml/factory/UnsupportedXmlImplementationTest.java
+++ 
b/src/test/java/org/apache/commons/xml/factory/UnsupportedXmlImplementationTest.java
@@ -23,7 +23,7 @@
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
-import org.apache.commons.xml.factory.internal.ProviderRegistry;
+import org.apache.commons.xml.factory.internal.CompositeProvider;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -66,7 +66,7 @@ public boolean getFeature(final String name) {
     void registryRejectsUnknownFactory() {
         final IllegalStateException thrown = assertThrows(
                 IllegalStateException.class,
-                () -> 
ProviderRegistry.getInstance().providerFor(FakeDocumentBuilderFactory.class));
+                () -> CompositeProvider.getInstance().configure(new 
FakeDocumentBuilderFactory()));
         assertNotNull(thrown.getMessage());
         
assertTrue(thrown.getMessage().contains(FakeDocumentBuilderFactory.class.getName()),
                 "Exception message must name the unsupported class: " + 
thrown.getMessage());
diff --git 
a/src/test/java/org/apache/commons/xml/factory/attacks/SaxonXPathExternalCallsTest.java
 
b/src/test/java/org/apache/commons/xml/factory/attacks/SaxonXPathExternalCallsTest.java
index 113cc37..6df71dd 100644
--- 
a/src/test/java/org/apache/commons/xml/factory/attacks/SaxonXPathExternalCallsTest.java
+++ 
b/src/test/java/org/apache/commons/xml/factory/attacks/SaxonXPathExternalCallsTest.java
@@ -20,8 +20,7 @@
 
 import javax.xml.xpath.XPathFactory;
 
-import org.apache.commons.xml.factory.internal.ProviderRegistry;
-import org.apache.commons.xml.factory.spi.XmlProvider;
+import org.apache.commons.xml.factory.internal.CompositeProvider;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
@@ -81,7 +80,6 @@ private static XPathFactory hardenedSaxonXPathFactory() {
         } catch (final ReflectiveOperationException e) {
             throw new AssertionError("Cannot instantiate " + 
SAXON_XPATH_FACTORY_CLASS, e);
         }
-        final XmlProvider provider = 
ProviderRegistry.getInstance().providerFor(xpf.getClass());
-        return provider.configure(xpf);
+        return CompositeProvider.getInstance().configure(xpf);
     }
 }
diff --git 
a/src/test/java/org/apache/commons/xml/factory/internal/CompositeProviderTest.java
 
b/src/test/java/org/apache/commons/xml/factory/internal/CompositeProviderTest.java
new file mode 100644
index 0000000..0fe3138
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/xml/factory/internal/CompositeProviderTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+package org.apache.commons.xml.factory.internal;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.commons.xml.factory.spi.XmlProvider;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Verifies composite dispatch without relying on any bundled provider.
+ *
+ * <p>Stub providers are injected through the package-private {@link 
CompositeProvider#CompositeProvider(java.util.List)} constructor so the tests 
exercise the
+ * lookup, dispatch and cache logic in isolation from the 
ServiceLoader-discovered classpath.</p>
+ */
+class CompositeProviderTest {
+
+    /**
+     * Stand-in factory whose concrete class is not matched by any bundled 
provider on the test classpath.
+     */
+    private static final class FakeFactory extends DocumentBuilderFactory {
+
+        @Override
+        public DocumentBuilder newDocumentBuilder() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setAttribute(final String name, final Object value) {
+            // no-op
+        }
+
+        @Override
+        public Object getAttribute(final String name) {
+            return null;
+        }
+
+        @Override
+        public void setFeature(final String name, final boolean value) {
+            // no-op
+        }
+
+        @Override
+        public boolean getFeature(final String name) {
+            return false;
+        }
+    }
+
+    /** Stub that records every {@code supports} and {@code configure} 
invocation and claims to support all classes. */
+    private static final class CountingMatchAll implements XmlProvider {
+
+        int supportsCount;
+
+        int configureCount;
+
+        @Override
+        public boolean supports(final Class<?> factoryClass) {
+            supportsCount++;
+            return true;
+        }
+
+        @Override
+        public DocumentBuilderFactory configure(final DocumentBuilderFactory 
factory) {
+            configureCount++;
+            return factory;
+        }
+    }
+
+    /** Stub that matches nothing. */
+    private static final class MatchesNone implements XmlProvider {
+        @Override
+        public boolean supports(final Class<?> factoryClass) {
+            return false;
+        }
+    }
+
+    @Test
+    void unknownFactoryClassThrowsHardeningException() {
+        final CompositeProvider composite = new 
CompositeProvider(Collections.emptyList());
+        final FakeFactory factory = new FakeFactory();
+        final HardeningException thrown = 
assertThrows(HardeningException.class, () -> composite.configure(factory));
+        assertTrue(thrown.getMessage().contains(FakeFactory.class.getName()),
+                "Exception message must name the unsupported class: " + 
thrown.getMessage());
+    }
+
+    @Test
+    void scansProvidersOnEveryInvocation() {
+        final CountingMatchAll provider = new CountingMatchAll();
+        final CompositeProvider composite = new 
CompositeProvider(Collections.singletonList(provider));
+        final FakeFactory factory = new FakeFactory();
+        composite.configure(factory);
+        composite.configure(factory);
+        assertEquals(2, provider.supportsCount, "supports() must be invoked 
once per configure call (no caching)");
+        assertEquals(2, provider.configureCount);
+    }
+
+    @Test
+    void bundledProvidersAreConsultedInDeclaredOrder() {
+        final CountingMatchAll winner = new CountingMatchAll();
+        final CountingMatchAll shadowed = new CountingMatchAll();
+        final CompositeProvider composite = new 
CompositeProvider(Arrays.asList(winner, shadowed));
+        final FakeFactory factory = new FakeFactory();
+        assertSame(factory, composite.configure(factory));
+        assertEquals(1, winner.configureCount);
+        assertEquals(0, shadowed.configureCount);
+    }
+
+    @Test
+    void nonMatchingBundledProviderFallsThroughToNext() {
+        final XmlProvider noMatch = new MatchesNone();
+        final CountingMatchAll match = new CountingMatchAll();
+        final CompositeProvider composite = new 
CompositeProvider(Arrays.asList(noMatch, match));
+        composite.configure(new FakeFactory());
+        assertEquals(1, match.configureCount);
+    }
+
+    @Test
+    void supportsAlwaysReturnsFalse() {
+        final CompositeProvider composite = new 
CompositeProvider(Collections.singletonList(new CountingMatchAll()));
+        assertFalse(composite.supports(FakeFactory.class));
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/xml/factory/internal/ProviderRegistryTest.java
 
b/src/test/java/org/apache/commons/xml/factory/internal/ProviderRegistryTest.java
deleted file mode 100644
index 318e627..0000000
--- 
a/src/test/java/org/apache/commons/xml/factory/internal/ProviderRegistryTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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
- *
- *      https://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.
- */
-package org.apache.commons.xml.factory.internal;
-
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-import org.apache.commons.xml.factory.spi.XmlProvider;
-import org.junit.jupiter.api.Test;
-
-/**
- * Verifies registry dispatch without relying on any bundled provider.
- *
- * <p>Stub providers are injected through the package-private {@link 
ProviderRegistry#ProviderRegistry(java.util.List)} constructor so the tests 
exercise the
- * lookup and cache logic in isolation from the ServiceLoader-discovered 
classpath.</p>
- */
-class ProviderRegistryTest {
-
-    /** Stub that claims to support every non-null factory class. */
-    private static final class MatchesAll implements XmlProvider {
-        @Override
-        public boolean supports(final Class<?> factoryClass) {
-            return factoryClass != null;
-        }
-    }
-
-    /** Stub that matches nothing. */
-    private static final class MatchesNone implements XmlProvider {
-        @Override
-        public boolean supports(final Class<?> factoryClass) {
-            return false;
-        }
-    }
-
-    @Test
-    void providerForNullThrowsNpe() {
-        final ProviderRegistry registry = new 
ProviderRegistry(Collections.emptyList());
-        assertThrows(NullPointerException.class, () -> 
registry.providerFor(null));
-    }
-
-    @Test
-    void providerForUnknownClassThrowsHardeningException() {
-        final ProviderRegistry registry = new 
ProviderRegistry(Collections.emptyList());
-        final HardeningException thrown = assertThrows(
-                HardeningException.class,
-                () -> registry.providerFor(String.class));
-        assertTrue(thrown.getMessage().contains(String.class.getName()),
-                "Exception message must name the unsupported class: " + 
thrown.getMessage());
-    }
-
-    @Test
-    void providerForCachesResolvedProvider() {
-        final XmlProvider stub = new MatchesAll();
-        final ProviderRegistry registry = new 
ProviderRegistry(Collections.singletonList(stub));
-        final XmlProvider first = registry.providerFor(String.class);
-        final XmlProvider second = registry.providerFor(String.class);
-        assertSame(first, second);
-        assertSame(stub, first);
-    }
-
-    @Test
-    void bundledProvidersAreConsultedInDeclaredOrder() {
-        final XmlProvider winner = new MatchesAll();
-        final XmlProvider shadowed = new MatchesAll();
-        final ProviderRegistry registry = new 
ProviderRegistry(Arrays.asList(winner, shadowed));
-        assertSame(winner, registry.providerFor(String.class));
-    }
-
-    @Test
-    void nonMatchingBundledProviderFallsThroughToNext() {
-        final XmlProvider noMatch = new MatchesNone();
-        final XmlProvider match = new MatchesAll();
-        final ProviderRegistry registry = new 
ProviderRegistry(Arrays.asList(noMatch, match));
-        assertSame(match, registry.providerFor(String.class));
-    }
-
-    @Test
-    void emptyBundledListWithNoServiceLoaderProvidersThrows() {
-        final ProviderRegistry registry = new 
ProviderRegistry(Collections.emptyList());
-        assertThrows(HardeningException.class,
-                () -> registry.providerFor(Integer.class));
-    }
-}

Reply via email to