diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CaseInsensitiveDictionary.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CaseInsensitiveDictionary.java new file mode 100644 index 0000000000..c476ad7246 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CaseInsensitiveDictionary.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.internal.util.collections; + +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; + +import org.hibernate.Internal; + +/** + * Wraps a ConcurrentHashMap having all keys as Strings + * and ensures all keys are lowercased. + * It does assume keys and arguments are never null, preferring to throw a NPE + * over adding unnecessary checks. + * The public exposed methods are similar to the ones on Map, but + * not all Map methods are exposed - only a selection we actually need; this + * implies it doesn't implement Map; nothing stops us to make it implement Map + * but at time of writing it seems unnecessary for our purposes. + * @param the type for the stored values. + */ +@Internal +public final class CaseInsensitiveDictionary { + + private final Map map = new ConcurrentHashMap<>(); + + public V get(final String key) { + return map.get( trueKey( key ) ); + } + + /** + * Contrary to traditional Map, we make the return unmodifiable. + * @return the map's keySet + */ + public Set unmodifiableKeySet() { + return Collections.unmodifiableSet( map.keySet() ); + } + + /** + * Contrary to traditional Map, we make the return unmodifiable. + * @return the map's entrySet + */ + public Set> unmodifiableEntrySet() { + return Collections.unmodifiableSet( map.entrySet() ); + } + + public V put(final String key, V value) { + return map.put( trueKey( key ), value ); + } + + public V remove(final String key) { + return map.remove( trueKey( key ) ); + } + + public boolean containsKey(final String key) { + return map.containsKey( trueKey( key ) ); + } + + private static String trueKey(final String key) { + return key.toLowerCase( Locale.ROOT ); + } + + public void clear() { + map.clear(); + } + + public void forEach(final BiConsumer action) { + map.forEach( action ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java index b861bb427a..0341e3edbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java @@ -6,11 +6,12 @@ */ package org.hibernate.query.sqm.function; -import java.util.AbstractMap; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.stream.Stream; +import org.hibernate.internal.util.collections.CaseInsensitiveDictionary; import org.hibernate.query.sqm.produce.function.FunctionParameterType; import org.hibernate.query.sqm.produce.function.NamedFunctionDescriptorBuilder; import org.hibernate.query.sqm.produce.function.PatternFunctionDescriptorBuilder; @@ -35,27 +36,31 @@ import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTyp public class SqmFunctionRegistry { private static final Logger log = Logger.getLogger( SqmFunctionRegistry.class ); - private final Map functionMap = new TreeMap<>( CASE_INSENSITIVE_ORDER ); - private final Map alternateKeyMap = new TreeMap<>( CASE_INSENSITIVE_ORDER ); + private final CaseInsensitiveDictionary functionMap = new CaseInsensitiveDictionary<>(); + private final CaseInsensitiveDictionary alternateKeyMap = new CaseInsensitiveDictionary<>(); public SqmFunctionRegistry() { log.trace( "SqmFunctionRegistry created" ); } - public Map getFunctions() { - return functionMap; + public Set getValidFunctionKeys() { + return functionMap.unmodifiableKeySet(); } + /** + * Useful for diagnostics - not efficient: do not use in production code. + * + * @return + */ public Stream> getFunctionsByName() { - return Stream.concat( - functionMap.entrySet().stream(), - alternateKeyMap.entrySet().stream().map( - entry -> new AbstractMap.SimpleEntry<>( - entry.getKey(), - functionMap.get( entry.getValue() ) - ) - ) - ); + final Map sortedFunctionMap = new TreeMap<>( CASE_INSENSITIVE_ORDER ); + for ( Map.Entry e : functionMap.unmodifiableEntrySet() ) { + sortedFunctionMap.put( e.getKey(), e.getValue() ); + } + for ( Map.Entry e : alternateKeyMap.unmodifiableEntrySet() ) { + sortedFunctionMap.put( e.getKey(), functionMap.get( e.getValue() ) ); + } + return sortedFunctionMap.entrySet().stream(); } /** diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java index e52b48a691..11e583e7c5 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java @@ -35,8 +35,7 @@ public abstract class SpatialSessionFactoryAware extends SpatialTestDataProvider this.supportedFunctions = scope.getSessionFactory() .getQueryEngine() .getSqmFunctionRegistry() - .getFunctions() - .keySet(); + .getValidFunctionKeys(); if ( DialectContext.getDialect() instanceof H2Dialect ) { initH2GISExtensionsForInMemDb(); }