001package gudusoft.gsqlparser.catalog.input; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.LinkedHashMap; 006import java.util.List; 007import java.util.Map; 008import java.util.ServiceLoader; 009import java.util.concurrent.atomic.AtomicBoolean; 010 011/** 012 * Look-up table for {@link CatalogInputReaderFactory}s registered via 013 * {@code META-INF/services/gudusoft.gsqlparser.catalog.input.CatalogInputReaderFactory} or 014 * programmatically via {@link #register(CatalogInputReaderFactory)}. 015 * 016 * <p>Plan §7.1 / §13.1. Process-global registry; lazily initialized from the 017 * {@link ServiceLoader}; programmatic registration takes precedence on 018 * {@link CatalogInputKind} collision.</p> 019 */ 020public final class CatalogInputReaders { 021 022 private static final Object MUTEX = new Object(); 023 private static final Map<CatalogInputKind, CatalogInputReaderFactory> FACTORIES = 024 new LinkedHashMap<CatalogInputKind, CatalogInputReaderFactory>(); 025 private static final AtomicBoolean LOADED = new AtomicBoolean(false); 026 027 private CatalogInputReaders() { 028 // Static utility — no instances. 029 } 030 031 public static void register(CatalogInputReaderFactory factory) { 032 if (factory == null) { 033 throw new IllegalArgumentException("CatalogInputReaders.register: factory is required"); 034 } 035 if (factory.kind() == null) { 036 throw new IllegalArgumentException( 037 "CatalogInputReaders.register: factory kind may not be null"); 038 } 039 synchronized (MUTEX) { 040 ensureLoaded(); 041 FACTORIES.put(factory.kind(), factory); 042 } 043 } 044 045 /** 046 * Unique-match selector. Walks all registered factories and returns the one whose 047 * reader {@code supports(...)} the source. Throws {@link CatalogInputException} on 048 * zero or multiple matches; the exception message lists the candidates so callers 049 * can disambiguate by setting {@link CatalogInputSource#declaredKind()}. 050 */ 051 public static CatalogInputReader forSource(CatalogInputSource source, 052 CatalogLoadOptions options) 053 throws CatalogInputException { 054 if (source == null) { 055 throw new CatalogInputException( 056 "CatalogInputReaders.forSource: source is required"); 057 } 058 synchronized (MUTEX) { 059 ensureLoaded(); 060 // Fast path: caller declared a kind that we've registered. 061 if (source.declaredKind() != null) { 062 CatalogInputReaderFactory direct = FACTORIES.get(source.declaredKind()); 063 if (direct != null) { 064 CatalogInputReader r = direct.create(); 065 if (r.supports(source, options)) { 066 return r; 067 } 068 } 069 } 070 // Generic walk: collect every reader that claims support. 071 List<CatalogInputReader> matches = new ArrayList<CatalogInputReader>(); 072 List<CatalogInputKind> matchedKinds = new ArrayList<CatalogInputKind>(); 073 for (CatalogInputReaderFactory f : FACTORIES.values()) { 074 CatalogInputReader r = f.create(); 075 if (r.supports(source, options)) { 076 matches.add(r); 077 matchedKinds.add(f.kind()); 078 } 079 } 080 if (matches.isEmpty()) { 081 throw new CatalogInputException( 082 "No CatalogInputReader matches source=" + source 083 + "; registered kinds=" + new ArrayList<CatalogInputKind>(FACTORIES.keySet())); 084 } 085 if (matches.size() > 1) { 086 throw new CatalogInputException( 087 "Ambiguous CatalogInputReader match for source=" + source 088 + "; candidates=" + matchedKinds 089 + ". Disambiguate by setting CatalogInputSource.declaredKind."); 090 } 091 return matches.get(0); 092 } 093 } 094 095 public static List<CatalogInputReaderFactory> registered() { 096 synchronized (MUTEX) { 097 ensureLoaded(); 098 return Collections.unmodifiableList( 099 new ArrayList<CatalogInputReaderFactory>(FACTORIES.values())); 100 } 101 } 102 103 /** Test-only hook: discard all registrations and reset the lazy-load flag. */ 104 static void clearForTesting() { 105 synchronized (MUTEX) { 106 FACTORIES.clear(); 107 LOADED.set(false); 108 } 109 } 110 111 private static void ensureLoaded() { 112 if (LOADED.compareAndSet(false, true)) { 113 for (CatalogInputReaderFactory f : ServiceLoader.load(CatalogInputReaderFactory.class)) { 114 if (f.kind() != null && !FACTORIES.containsKey(f.kind())) { 115 FACTORIES.put(f.kind(), f); 116 } 117 } 118 } 119 } 120}