[jOOQ/jOOQ#19003] Make DefaultExecuteContext available to internals via
ThreadLocal
This commit is contained in:
parent
08f853b002
commit
e51d224da3
@ -276,7 +276,7 @@ abstract class AbstractQuery<R extends Record> extends AbstractAttachableQueryPa
|
||||
// in case this Query / Configuration was previously
|
||||
// deserialised
|
||||
DefaultExecuteContext ctx = new DefaultExecuteContext(c, this);
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx);
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx, true);
|
||||
|
||||
int result = 0;
|
||||
try {
|
||||
|
||||
@ -650,7 +650,7 @@ implements
|
||||
|
||||
private final int executeCallableStatement() {
|
||||
ExecuteContext ctx = new DefaultExecuteContext(configuration, this);
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx);
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx, true);
|
||||
|
||||
try {
|
||||
// [#8968] Keep start() event inside of lifecycle management
|
||||
|
||||
@ -105,7 +105,7 @@ final class BatchMultiple extends AbstractBatch {
|
||||
return Stream.of(queries).mapToInt(configuration.dsl()::execute).toArray();
|
||||
|
||||
DefaultExecuteContext ctx = new DefaultExecuteContext(configuration, BatchMode.MULTIPLE, queries);
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx);
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx, true);
|
||||
|
||||
try {
|
||||
|
||||
|
||||
@ -199,7 +199,7 @@ final class BatchSingle extends AbstractBatch implements BatchBindStep {
|
||||
|
||||
private final int[] executePrepared() {
|
||||
DefaultExecuteContext ctx = new DefaultExecuteContext(configuration, BatchMode.SINGLE, new Query[] { query });
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx);
|
||||
ExecuteListener listener = ExecuteListeners.get(ctx, true);
|
||||
|
||||
try {
|
||||
// [#8968] Keep start() event inside of lifecycle management
|
||||
|
||||
@ -41,11 +41,9 @@ import static org.jooq.tools.StringUtils.defaultIfNull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jooq.impl.CacheType;
|
||||
import org.jooq.Configuration;
|
||||
|
||||
/**
|
||||
@ -74,17 +72,38 @@ final class Cache {
|
||||
if (!type.category.predicate.test(configuration.settings()))
|
||||
return operation.get();
|
||||
|
||||
Object cacheOrNull = configuration.data(type);
|
||||
if (cacheOrNull == null) {
|
||||
synchronized (type) {
|
||||
cacheOrNull = configuration.data(type);
|
||||
Object cacheOrNull = null;
|
||||
DefaultExecuteContext ctx = null;
|
||||
|
||||
if (cacheOrNull == null)
|
||||
configuration.data(type, cacheOrNull = defaultIfNull(
|
||||
configuration.cacheProvider().provide(new DefaultCacheContext(configuration, type)),
|
||||
NULL
|
||||
));
|
||||
// [#19003] If a cache type allows for being lazy copied, then the current ExecuteContext
|
||||
// may hold a smaller instance of the cache containing only the data relevant
|
||||
// to the current execution, not the globally available data. Accessing this
|
||||
// thread-bound local copy is much faster as there is no contention among threads
|
||||
// when the data is being accessed repeatedly, as for example DefaultRecordMapper
|
||||
// instances when using heavily nested collection mapping.
|
||||
if (type.lazyCopy)
|
||||
ctx = DefaultExecuteContext.globalExecuteContext();
|
||||
|
||||
if (ctx != null)
|
||||
cacheOrNull = ctx.data(type);
|
||||
|
||||
if (cacheOrNull == null) {
|
||||
cacheOrNull = configuration.data(type);
|
||||
|
||||
if (cacheOrNull == null) {
|
||||
synchronized (type) {
|
||||
cacheOrNull = configuration.data(type);
|
||||
|
||||
if (cacheOrNull == null)
|
||||
configuration.data(type, cacheOrNull = defaultIfNull(
|
||||
configuration.cacheProvider().provide(new DefaultCacheContext(configuration, type)),
|
||||
NULL
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx != null && cacheOrNull != NULL)
|
||||
ctx.data(type, cacheOrNull = new LazyCopyMap<>((Map<Object, Object>) cacheOrNull));
|
||||
}
|
||||
|
||||
if (cacheOrNull == NULL)
|
||||
@ -94,8 +113,6 @@ final class Cache {
|
||||
// contract. However since we cannot use ConcurrentHashMap.computeIfAbsent()
|
||||
// recursively, we have to revert to double checked locking nonetheless.
|
||||
// See also: https://stackoverflow.com/q/28840047/521799
|
||||
// [#18999] Our new ConcurrentReadWriteMap could deadlock when nesting writes
|
||||
// in internal iterations
|
||||
Map<Object, Object> cache = (Map<Object, Object>) cacheOrNull;
|
||||
Object k = key.get();
|
||||
Object v = cache.get(k);
|
||||
|
||||
@ -42,11 +42,13 @@ import static org.jooq.impl.CacheType.CacheCategory.PARSING_CONNECTION;
|
||||
import static org.jooq.impl.CacheType.CacheCategory.RECORD_MAPPER;
|
||||
import static org.jooq.impl.CacheType.CacheCategory.REFLECTION;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jooq.CacheProvider;
|
||||
import org.jooq.Configuration;
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.ExecuteContext;
|
||||
import org.jooq.RecordMapper;
|
||||
import org.jooq.RecordType;
|
||||
import org.jooq.conf.Settings;
|
||||
@ -72,63 +74,78 @@ public enum CacheType {
|
||||
* A reflection cache for lookups of JPA annotated getters in
|
||||
* {@link DefaultRecordMapper}.
|
||||
*/
|
||||
REFLECTION_CACHE_GET_ANNOTATED_GETTER(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-getter"),
|
||||
REFLECTION_CACHE_GET_ANNOTATED_GETTER(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-getter", false),
|
||||
|
||||
/**
|
||||
* A reflection cache for lookups of JPA annotated members in
|
||||
* {@link DefaultRecordMapper}.
|
||||
*/
|
||||
REFLECTION_CACHE_GET_ANNOTATED_MEMBERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-members"),
|
||||
REFLECTION_CACHE_GET_ANNOTATED_MEMBERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-members", false),
|
||||
|
||||
/**
|
||||
* A reflection cache for lookups of JPA annotated setters in
|
||||
* {@link DefaultRecordMapper}.
|
||||
*/
|
||||
REFLECTION_CACHE_GET_ANNOTATED_SETTERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-setters"),
|
||||
REFLECTION_CACHE_GET_ANNOTATED_SETTERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-annotated-setters", false),
|
||||
|
||||
/**
|
||||
* A reflection cache for lookups of getters matched by name in
|
||||
* {@link DefaultRecordMapper}.
|
||||
*/
|
||||
REFLECTION_CACHE_GET_MATCHING_GETTER(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-getter"),
|
||||
REFLECTION_CACHE_GET_MATCHING_GETTER(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-getter", false),
|
||||
|
||||
/**
|
||||
* A reflection cache for lookups of members matched by name in
|
||||
* {@link DefaultRecordMapper}.
|
||||
*/
|
||||
REFLECTION_CACHE_GET_MATCHING_MEMBERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-members"),
|
||||
REFLECTION_CACHE_GET_MATCHING_MEMBERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-members", false),
|
||||
|
||||
/**
|
||||
* A reflection cache for lookups of setters matched by name in
|
||||
* {@link DefaultRecordMapper}.
|
||||
*/
|
||||
REFLECTION_CACHE_GET_MATCHING_SETTERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-setters"),
|
||||
REFLECTION_CACHE_GET_MATCHING_SETTERS(REFLECTION, "org.jooq.configuration.reflection-cache.get-matching-setters", false),
|
||||
|
||||
/**
|
||||
* A reflection cache to check if a type has any JPA annotations at all, in
|
||||
* {@link DefaultRecordMapper}.
|
||||
*/
|
||||
REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS(REFLECTION, "org.jooq.configuration.reflection-cache.has-column-annotations"),
|
||||
REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS(REFLECTION, "org.jooq.configuration.reflection-cache.has-column-annotations", false),
|
||||
|
||||
/**
|
||||
* A cache used by the {@link DefaultRecordMapperProvider} to cache all
|
||||
* {@link RecordMapper} instances and their possibly expensive
|
||||
* initialisations per {@link RecordType} and {@link Class} pairs.
|
||||
*/
|
||||
CACHE_RECORD_MAPPERS(RECORD_MAPPER, "org.jooq.configuration.cache.record-mappers"),
|
||||
CACHE_RECORD_MAPPERS(RECORD_MAPPER, "org.jooq.configuration.cache.record-mappers", true),
|
||||
|
||||
/**
|
||||
* [#8334] A cache for SQL to SQL translations in the
|
||||
* {@link DSLContext#parsingConnection()}, to speed up its usage.
|
||||
*/
|
||||
CACHE_PARSING_CONNECTION(PARSING_CONNECTION, "org.jooq.configuration.cache.parsing-connection");
|
||||
CACHE_PARSING_CONNECTION(PARSING_CONNECTION, "org.jooq.configuration.cache.parsing-connection", false);
|
||||
|
||||
/**
|
||||
* The category of the cache, indicating the logic that enables / disables
|
||||
* it.
|
||||
*/
|
||||
final CacheCategory category;
|
||||
|
||||
/**
|
||||
* The cache key in the backing {@link ConcurrentMap}.
|
||||
*/
|
||||
final String key;
|
||||
|
||||
CacheType(CacheCategory category, String key) {
|
||||
/**
|
||||
* Whether the backing {@link ConcurrentMap} should be lazy copied in the
|
||||
* scope of an {@link ExecuteContext} to reduce contention.
|
||||
*/
|
||||
final boolean lazyCopy;
|
||||
|
||||
CacheType(CacheCategory category, String key, boolean lazyCopy) {
|
||||
this.category = category;
|
||||
this.key = key;
|
||||
this.lazyCopy = lazyCopy;
|
||||
}
|
||||
|
||||
enum CacheCategory {
|
||||
|
||||
@ -122,7 +122,7 @@ final class CursorImpl<R extends Record> extends AbstractCursor<R> {
|
||||
super(ctx.configuration(), (AbstractRow<R>) Tools.row0(fields));
|
||||
|
||||
this.ctx = ctx;
|
||||
this.listener = (listener != null ? listener : ExecuteListeners.getAndStart(ctx));
|
||||
this.listener = (listener != null ? listener : ExecuteListeners.getAndStart(ctx, true));
|
||||
this.factory = recordFactory(table, type, this.fields);
|
||||
this.keepStatement = keepStatement;
|
||||
this.keepResultSet = keepResultSet;
|
||||
|
||||
@ -1300,7 +1300,7 @@ public class DefaultDSLContext extends AbstractScope implements DSLContext, Seri
|
||||
@Override
|
||||
public Cursor<Record> fetchLazy(ResultSet rs, Field<?>... fields) {
|
||||
ExecuteContext ctx = new DefaultExecuteContext(configuration());
|
||||
ExecuteListener listener = ExecuteListeners.getAndStart(ctx);
|
||||
ExecuteListener listener = ExecuteListeners.getAndStart(ctx, true);
|
||||
|
||||
ctx.resultSet(rs);
|
||||
return new CursorImpl<>(ctx, listener, fields, false, true);
|
||||
|
||||
@ -196,6 +196,7 @@ class DefaultExecuteContext implements ExecuteContext {
|
||||
}
|
||||
|
||||
LOCAL_CONNECTION.remove();
|
||||
GLOBAL_EXECUTE_CONTEXT.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -244,7 +245,7 @@ class DefaultExecuteContext implements ExecuteContext {
|
||||
// XXX: Static utility methods for handling Configuration lifecycle
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
private static final ThreadLocal<ExecuteContext> LOCAL_EXECUTE_CONTEXT = new ThreadLocal<>();
|
||||
static final ThreadLocal<DefaultExecuteContext> GLOBAL_EXECUTE_CONTEXT = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* Get the registered {@link ExecuteContext}.
|
||||
@ -253,20 +254,41 @@ class DefaultExecuteContext implements ExecuteContext {
|
||||
* {@link ExecuteContext} has been established, until the statement is
|
||||
* closed.
|
||||
*/
|
||||
static final DefaultExecuteContext globalExecuteContext() {
|
||||
return GLOBAL_EXECUTE_CONTEXT.get();
|
||||
}
|
||||
|
||||
private static final ThreadLocal<ExecuteContext> LOCAL_EXECUTE_CONTEXT = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* Get the registered {@link ExecuteContext}.
|
||||
*
|
||||
* @deprecated - [#19003] - 3.21.0 - This is no longer necessary, now that
|
||||
* we have {@link #globalExecuteContext()}.
|
||||
*/
|
||||
@Deprecated
|
||||
static final ExecuteContext localExecuteContext() {
|
||||
return LOCAL_EXECUTE_CONTEXT.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a runnable with a new {@link #localExecuteContext()}.
|
||||
*
|
||||
* @deprecated - [#19003] - 3.21.0 - This is no longer necessary, now that
|
||||
* we have {@link #globalExecuteContext()}.
|
||||
*/
|
||||
@Deprecated
|
||||
static final <E extends Exception> void localExecuteContext(ExecuteContext ctx, ThrowingRunnable<E> runnable) throws E {
|
||||
localExecuteContext(ctx, () -> { runnable.run(); return null; });
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a supplier with a new {@link #localExecuteContext()}.
|
||||
*
|
||||
* @deprecated - [#19003] - 3.21.0 - This is no longer necessary, now that
|
||||
* we have {@link #globalExecuteContext()}.
|
||||
*/
|
||||
@Deprecated
|
||||
static final <T, E extends Exception> T localExecuteContext(ExecuteContext ctx, ThrowingSupplier<T, E> supplier) throws E {
|
||||
ExecuteContext old = localExecuteContext();
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ package org.jooq.impl;
|
||||
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static org.jooq.conf.InvocationOrder.REVERSE;
|
||||
import static org.jooq.impl.DefaultExecuteContext.GLOBAL_EXECUTE_CONTEXT;
|
||||
import static org.jooq.impl.Tools.EMPTY_EXECUTE_LISTENER;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -67,6 +68,7 @@ final class ExecuteListeners implements ExecuteListener {
|
||||
|
||||
|
||||
private final ExecuteListener[][] listeners;
|
||||
private final boolean threadbound;
|
||||
|
||||
// In some setups, these two events may get mixed up chronologically by the
|
||||
// Cursor. Postpone fetchEnd event until after resultEnd event, if there is
|
||||
@ -76,14 +78,18 @@ final class ExecuteListeners implements ExecuteListener {
|
||||
|
||||
/**
|
||||
* Initialise the provided {@link ExecuteListener} set and return a wrapper.
|
||||
*
|
||||
* @param ctx The {@link ExecuteContext}.
|
||||
* @param threadbound Whether the {@link ExecuteContext} is thread bound,
|
||||
* e.g. in ordinary JDBC use-cases, not reactive execution.
|
||||
*/
|
||||
static ExecuteListener get(ExecuteContext ctx) {
|
||||
static final ExecuteListener get(ExecuteContext ctx, boolean threadbound) {
|
||||
ExecuteListener[][] listeners = listeners(ctx);
|
||||
|
||||
if (listeners == null)
|
||||
return EMPTY_LISTENER;
|
||||
else
|
||||
return new ExecuteListeners(listeners);
|
||||
return new ExecuteListeners(listeners, threadbound);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,9 +97,13 @@ final class ExecuteListeners implements ExecuteListener {
|
||||
* <p>
|
||||
* Call this if the {@link ExecuteListener#start(ExecuteContext)} event
|
||||
* should be triggered eagerly.
|
||||
*
|
||||
* @param ctx The {@link ExecuteContext}.
|
||||
* @param threadbound Whether the {@link ExecuteContext} is thread bound,
|
||||
* e.g. in ordinary JDBC use-cases, not reactive execution.
|
||||
*/
|
||||
static ExecuteListener getAndStart(ExecuteContext ctx) {
|
||||
ExecuteListener result = get(ctx);
|
||||
static final ExecuteListener getAndStart(ExecuteContext ctx, boolean threadbound) {
|
||||
ExecuteListener result = get(ctx, threadbound);
|
||||
result.start(ctx);
|
||||
return result;
|
||||
}
|
||||
@ -150,12 +160,16 @@ final class ExecuteListeners implements ExecuteListener {
|
||||
return result == null ? new ArrayList<>() : result;
|
||||
}
|
||||
|
||||
private ExecuteListeners(ExecuteListener[][] listeners) {
|
||||
private ExecuteListeners(ExecuteListener[][] listeners, boolean threadbound) {
|
||||
this.listeners = listeners;
|
||||
this.threadbound = threadbound;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void start(ExecuteContext ctx) {
|
||||
if (threadbound && ctx instanceof DefaultExecuteContext c)
|
||||
GLOBAL_EXECUTE_CONTEXT.set(c);
|
||||
|
||||
for (ExecuteListener listener : listeners[0])
|
||||
listener.start(ctx);
|
||||
}
|
||||
@ -296,6 +310,8 @@ final class ExecuteListeners implements ExecuteListener {
|
||||
public final void end(ExecuteContext ctx) {
|
||||
for (ExecuteListener listener : listeners[1])
|
||||
listener.end(ctx);
|
||||
|
||||
GLOBAL_EXECUTE_CONTEXT.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
260
jOOQ/src/main/java/org/jooq/impl/LazyCopyMap.java
Normal file
260
jOOQ/src/main/java/org/jooq/impl/LazyCopyMap.java
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Licensed 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.
|
||||
*
|
||||
* Other licenses:
|
||||
* -----------------------------------------------------------------------------
|
||||
* Commercial licenses for this work are available. These replace the above
|
||||
* Apache-2.0 license and offer limited warranties, support, maintenance, and
|
||||
* commercial database integrations.
|
||||
*
|
||||
* For more information, please visit: https://www.jooq.org/legal/licensing
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
package org.jooq.impl;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jooq.ExecuteContext;
|
||||
|
||||
|
||||
/**
|
||||
* A lazy copy of a backing map, typically to speed up cache access locally, for
|
||||
* example for the scope of an {@link ExecuteContext}.
|
||||
* <p>
|
||||
* The lazy copy map may not be in sync with the backing map, meaning that if
|
||||
* the backing map is an {@link LRUCache}, or a similar cache like structure
|
||||
* with eviction policies, it may be the case that an eviction happens in the
|
||||
* backing map, but not in the local copy. While methods like {@link Map#size()}
|
||||
* or {@link Map#entrySet()} will call through to the backing data structure,
|
||||
* {@link Map#get(Object)} or {@link Map#containsKey(Object)} may not, but find
|
||||
* a stale object that is no longer in the backing cache. The assumption here is
|
||||
* that in the cache use-case, the backing cache would fill up the the same
|
||||
* missing value again in case of a {@link Map#get(Object)} call, evicting
|
||||
* another entry. These caveats will not produce any issues if only "cache
|
||||
* relevant" methods like {@link Map#get(Object)} and
|
||||
* {@link Map#put(Object, Object)} are called.
|
||||
* <p>
|
||||
* The same is true if the backing map is modified for any other reason.
|
||||
* <p>
|
||||
* This map is not thread safe, although it may reference a thread safe backing
|
||||
* map. It is intended for thread local usage.
|
||||
* <p>
|
||||
* This map does not support <code>null</code> keys or values.
|
||||
*
|
||||
* @author Lukas Eder
|
||||
*/
|
||||
final class LazyCopyMap<K, V> extends AbstractMap<K, V> {
|
||||
|
||||
final Map<K, V> copy;
|
||||
final Map<K, V> delegate;
|
||||
|
||||
LazyCopyMap(Map<K, V> delegate) {
|
||||
this.copy = new HashMap<>();
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
if (key == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
V value = copy.get(key);
|
||||
|
||||
if (value == null) {
|
||||
value = delegate.get(key);
|
||||
|
||||
if (value != null)
|
||||
copy.put((K) key, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
if (key == null || value == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
copy.put(key, value);
|
||||
return delegate.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
|
||||
// [#19003] To keep things simple, Iterator::remove isn't supported
|
||||
// [#19003] TODO: This currently doesn't copy contents, but it could
|
||||
return Collections.unmodifiableSet(delegate.entrySet());
|
||||
}
|
||||
|
||||
// keySet() and valueSet() from AbstractMap call through to entrySet()
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(K key, V value) {
|
||||
if (key == null || value == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
get(key);
|
||||
copy.putIfAbsent(key, value);
|
||||
return delegate.putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(K key, V oldValue, V newValue) {
|
||||
if (key == null || oldValue == null || newValue == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
copy.replace(key, oldValue, newValue);
|
||||
return delegate.replace(key, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(K key, V value) {
|
||||
if (key == null || value == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
copy.replace(key, value);
|
||||
return delegate.replace(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
|
||||
copy.replaceAll(function);
|
||||
delegate.replaceAll(function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
|
||||
if (key == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
get(key);
|
||||
copy.computeIfAbsent(key, mappingFunction);
|
||||
return delegate.computeIfAbsent(key, mappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
if (key == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
get(key);
|
||||
copy.computeIfPresent(key, remappingFunction);
|
||||
return delegate.computeIfPresent(key, remappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
if (key == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
get(key);
|
||||
copy.compute(key, remappingFunction);
|
||||
return delegate.compute(key, remappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
|
||||
if (key == null || value == null)
|
||||
throw new NullPointerException();
|
||||
|
||||
get(key);
|
||||
copy.merge(key, value, remappingFunction);
|
||||
return delegate.merge(key, value, remappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return copy.isEmpty()
|
||||
&& delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return copy.containsValue(value)
|
||||
|| delegate.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return copy.containsKey(key)
|
||||
|| delegate.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
copy.remove(key);
|
||||
return delegate.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
copy.remove(key, value);
|
||||
return delegate.remove(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
copy.putAll(m);
|
||||
delegate.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
copy.clear();
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return delegate.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -7814,11 +7814,18 @@ final class Tools {
|
||||
}
|
||||
|
||||
static final ConverterContext converterContext(Attachable attachable) {
|
||||
return new DefaultConverterContext(configuration(attachable));
|
||||
return converterContext(configuration(attachable));
|
||||
}
|
||||
|
||||
static final ConverterContext converterContext(Configuration configuration) {
|
||||
return new DefaultConverterContext(configuration(configuration));
|
||||
|
||||
// [#19003] Allow for accessing a cached instance of the ConverterContext, where available.
|
||||
DefaultExecuteContext c = DefaultExecuteContext.globalExecuteContext();
|
||||
|
||||
if (c != null && c.originalConfiguration() == configuration)
|
||||
return c.converterContext();
|
||||
else
|
||||
return new DefaultConverterContext(configuration(configuration));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user