From dba11edf0e1020f4600dea92d372183e23226c6d Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 15 Sep 2023 19:03:17 +0200 Subject: [PATCH] HHH-17282 Introduce a specialized Map for NavigablePath to Initializer --- .../results/internal/InitializersList.java | 7 +- .../NavigablePathMapToInitializer.java | 90 +++++++++++++++++++ .../sql/results/internal/ResultsHelper.java | 24 +---- 3 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/results/internal/NavigablePathMapToInitializer.java diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java index 7fa5687b21..1a75e9734e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/InitializersList.java @@ -9,7 +9,6 @@ package org.hibernate.sql.results.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -36,14 +35,14 @@ public final class InitializersList { private final Initializer[] sortedNonCollectionsFirst; private final Initializer[] sortedForResolveInstance; private final boolean hasCollectionInitializers; - private final Map initializerMap; + private final NavigablePathMapToInitializer initializerMap; private InitializersList( Initializer[] initializers, Initializer[] sortedNonCollectionsFirst, Initializer[] sortedForResolveInstance, boolean hasCollectionInitializers, - Map initializerMap) { + NavigablePathMapToInitializer initializerMap) { this.initializers = initializers; this.sortedNonCollectionsFirst = sortedNonCollectionsFirst; this.sortedForResolveInstance = sortedForResolveInstance; @@ -123,7 +122,7 @@ public final class InitializersList { && !(initializer instanceof AbstractBatchEntitySelectFetchInitializer ); } - InitializersList build(final Map initializerMap) { + InitializersList build(final NavigablePathMapToInitializer initializerMap) { final int size = initializers.size(); final Initializer[] sortedNonCollectionsFirst = new Initializer[size]; final Initializer[] sortedForResolveInstance = new Initializer[size]; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/NavigablePathMapToInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/NavigablePathMapToInitializer.java new file mode 100644 index 0000000000..b9f500b197 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/NavigablePathMapToInitializer.java @@ -0,0 +1,90 @@ +/* + * 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.sql.results.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.ResultsLogger; +import org.hibernate.sql.results.graph.Initializer; + + +/** + * This is in all practical terms a {@code Map} + * but wrapping an HashMap so to keep the client code readable as we need + * to: + * a) have a way to log all initializers + * b) prevent type pollution from happening on Initializer retrieval + * I also consider it good practice to only expose the minimal set of + * operations the client actually needs. + */ +public final class NavigablePathMapToInitializer { + + private HashMap map = null; + + public Initializer get(final NavigablePath navigablePath) { + if ( map != null && navigablePath != null ) { + final InitializerHolder h = map.get( navigablePath ); + if ( h != null ) { + return h.initializer; + } + } + return null; + } + + public void put(final NavigablePath navigablePath, final Initializer initializer) { + Objects.requireNonNull( navigablePath ); + Objects.requireNonNull( initializer ); + if ( map == null ) { + map = new HashMap<>(); + } + map.put( navigablePath, new InitializerHolder( initializer ) ); + } + + public void logInitializers() { + ResultsLogger logger = ResultsLogger.RESULTS_MESSAGE_LOGGER; + if ( !logger.isDebugEnabled() ) { + return; + } + if ( map == null ) { + logger.debug( "Initializer list is empty" ); + } + else { + //Apparently we want to log this on multiple lines (existing code did this - not sure if that was by design): + //using a StringBuilder to avoid potentially interleaving the logs from different operations. + final StringBuilder sb = new StringBuilder( "Initializer list:\n" ); + for ( Map.Entry holderEntry : map.entrySet() ) { + final NavigablePath navigablePath = holderEntry.getKey(); + final Initializer initializer = holderEntry.getValue().initializer; + String formatted = String.format( + " %s -> %s@%s (%s)", + navigablePath, + initializer, + initializer.hashCode(), + initializer.getInitializedPart() + ); + sb.append( '\t' ); + sb.append( formatted ); + sb.append( '\n' ); + } + logger.debug( sb.toString() ); + } + } + + //Custom holder to avoid type pollution: + //we make the type explicit, and this is a concrete class. + private static final class InitializerHolder { + final Initializer initializer; + + private InitializerHolder(final Initializer init) { + this.initializer = init; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java index ee9801ff9b..46bcd2dc4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/ResultsHelper.java @@ -6,9 +6,7 @@ */ package org.hibernate.sql.results.internal; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.function.Supplier; import org.hibernate.CacheMode; @@ -70,7 +68,8 @@ public class ResultsHelper { JdbcValuesMapping jdbcValuesMapping) { final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - final Map initializerMap = new LinkedHashMap<>(); + //custom Map + final NavigablePathMapToInitializer initializerMap = new NavigablePathMapToInitializer(); final InitializersList.Builder initializersBuilder = new InitializersList.Builder(); final List> assemblers = jdbcValuesMapping.resolveAssemblers( @@ -133,30 +132,13 @@ public class ResultsHelper { } ); - logInitializers( initializerMap ); + initializerMap.logInitializers(); final InitializersList initializersList = initializersBuilder.build( initializerMap ); return new StandardRowReader<>( assemblers, initializersList, rowTransformer, transformedResultJavaType ); } - private static void logInitializers(Map initializerMap) { - if ( ! ResultsLogger.RESULTS_MESSAGE_LOGGER.isDebugEnabled() ) { - return; - } - - ResultsLogger.RESULTS_MESSAGE_LOGGER.debug( "Initializer list" ); - initializerMap.forEach( (navigablePath, initializer) -> { - ResultsLogger.RESULTS_MESSAGE_LOGGER.debugf( - " %s -> %s@%s (%s)", - navigablePath, - initializer, - initializer.hashCode(), - initializer.getInitializedPart() - ); - } ); - } - public static void finalizeCollectionLoading( PersistenceContext persistenceContext, CollectionPersister collectionDescriptor,