001package gudusoft.gsqlparser.catalog.runtime; 002 003import gudusoft.gsqlparser.catalog.diagnostic.CatalogException; 004 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.LinkedHashMap; 008import java.util.List; 009import java.util.Map; 010import java.util.ServiceLoader; 011import java.util.concurrent.atomic.AtomicBoolean; 012 013/** 014 * Look-up table for {@link CatalogProviderFactory}s registered via 015 * {@code META-INF/services/gudusoft.gsqlparser.catalog.runtime.CatalogProviderFactory} or 016 * programmatically via {@link #register(CatalogProviderFactory)}. 017 * 018 * <p>Plan §7.2 / §13.1. The registry is process-global, lazily initialized from the 019 * {@link ServiceLoader}, and synchronized on every mutator. Programmatic registration 020 * replaces any previous registration with the same {@link CatalogProviderId}.</p> 021 */ 022public final class CatalogProviderRegistry { 023 024 private static final Object MUTEX = new Object(); 025 private static final Map<CatalogProviderId, CatalogProviderFactory> FACTORIES = 026 new LinkedHashMap<CatalogProviderId, CatalogProviderFactory>(); 027 private static final AtomicBoolean LOADED = new AtomicBoolean(false); 028 029 private CatalogProviderRegistry() { 030 // Static utility — no instances. 031 } 032 033 public static void register(CatalogProviderFactory factory) { 034 if (factory == null) { 035 throw new IllegalArgumentException("CatalogProviderRegistry.register: factory is required"); 036 } 037 if (factory.id() == null) { 038 throw new IllegalArgumentException( 039 "CatalogProviderRegistry.register: factory id may not be null"); 040 } 041 synchronized (MUTEX) { 042 ensureLoaded(); 043 FACTORIES.put(factory.id(), factory); 044 } 045 } 046 047 /** 048 * Look up the factory for {@code id} and create a new {@link CatalogProvider}. Throws 049 * {@link CatalogException} when no factory matches — callers should ensure the 050 * factory is registered before invoking. 051 */ 052 public static CatalogProvider create(CatalogProviderId id) { 053 if (id == null) { 054 throw new IllegalArgumentException("CatalogProviderRegistry.create: id is required"); 055 } 056 synchronized (MUTEX) { 057 ensureLoaded(); 058 CatalogProviderFactory factory = FACTORIES.get(id); 059 if (factory == null) { 060 throw new CatalogException( 061 "No CatalogProviderFactory registered for id=" + id 062 + "; registered=" + new ArrayList<CatalogProviderId>(FACTORIES.keySet())); 063 } 064 return factory.create(); 065 } 066 } 067 068 public static List<CatalogProviderFactory> registered() { 069 synchronized (MUTEX) { 070 ensureLoaded(); 071 return Collections.unmodifiableList( 072 new ArrayList<CatalogProviderFactory>(FACTORIES.values())); 073 } 074 } 075 076 /** Test-only hook: discard all registrations and reset the lazy load flag. */ 077 static void clearForTesting() { 078 synchronized (MUTEX) { 079 FACTORIES.clear(); 080 LOADED.set(false); 081 } 082 } 083 084 private static void ensureLoaded() { 085 if (LOADED.compareAndSet(false, true)) { 086 for (CatalogProviderFactory f : ServiceLoader.load(CatalogProviderFactory.class)) { 087 if (f.id() != null && !FACTORIES.containsKey(f.id())) { 088 FACTORIES.put(f.id(), f); 089 } 090 } 091 } 092 } 093}