| package com.fasterxml.jackson.databind.cfg; |
| |
| import java.util.*; |
| |
| /** |
| * Helper class used for storing and accessing per-call attributes. |
| * Storage is two-layered: at higher precedence, we have actual per-call |
| * attributes; and at lower precedence, default attributes that may be |
| * defined for Object readers and writers. |
| *<p> |
| * Note that the way mutability is implemented differs between kinds |
| * of attributes, to account for thread-safety: per-call attributes |
| * are handled assuming that instances are never shared, whereas |
| * changes to per-reader/per-writer attributes are made assuming |
| * sharing, by creating new copies instead of modifying state. |
| * This allows sharing of default values without per-call copying, but |
| * requires two-level lookup on access. |
| * |
| * @since 2.3 |
| */ |
| public abstract class ContextAttributes |
| { |
| public static ContextAttributes getEmpty() { |
| return Impl.getEmpty(); |
| } |
| |
| /* |
| /********************************************************** |
| /* Per-reader/writer access |
| /********************************************************** |
| */ |
| |
| public abstract ContextAttributes withSharedAttribute(Object key, Object value); |
| |
| public abstract ContextAttributes withSharedAttributes(Map<?,?> attributes); |
| |
| public abstract ContextAttributes withoutSharedAttribute(Object key); |
| |
| /* |
| /********************************************************** |
| /* Per-operation (serialize/deserialize) access |
| /********************************************************** |
| */ |
| |
| /** |
| * Accessor for value of specified attribute |
| */ |
| public abstract Object getAttribute(Object key); |
| |
| /** |
| * Mutator used during call (via context) to set value of "non-shared" |
| * part of attribute set. |
| */ |
| public abstract ContextAttributes withPerCallAttribute(Object key, Object value); |
| |
| /* |
| /********************************************************** |
| /* Default implementation |
| /********************************************************** |
| */ |
| |
| public static class Impl extends ContextAttributes |
| implements java.io.Serializable // just so ObjectReader/ObjectWriter can retain configs |
| { |
| private static final long serialVersionUID = 1L; |
| |
| protected final static Impl EMPTY = new Impl(Collections.emptyMap()); |
| |
| protected final static Object NULL_SURROGATE = new Object(); |
| |
| /** |
| * Shared attributes that we can not modify in-place. |
| */ |
| protected final Map<?,?> _shared; |
| |
| /** |
| * Per-call attributes that we can directly modify, since they are not |
| * shared between threads. |
| *<p> |
| * NOTE: typed as Object-to-Object, unlike {@link #_shared}, because |
| * we need to be able to modify contents, and wildcard type would |
| * complicate that access. |
| */ |
| protected transient Map<Object,Object> _nonShared; |
| |
| /* |
| /********************************************************** |
| /* Construction, factory methods |
| /********************************************************** |
| */ |
| |
| protected Impl(Map<?,?> shared) { |
| _shared = shared; |
| _nonShared = null; |
| } |
| |
| protected Impl(Map<?,?> shared, Map<Object,Object> nonShared) { |
| _shared = shared; |
| _nonShared = nonShared; |
| } |
| |
| public static ContextAttributes getEmpty() { |
| return EMPTY; |
| } |
| |
| /* |
| /********************************************************** |
| /* Per-reader/writer mutant factories |
| /********************************************************** |
| */ |
| |
| @Override |
| public ContextAttributes withSharedAttribute(Object key, Object value) |
| { |
| Map<Object,Object> m; |
| // need to cover one special case, since EMPTY uses Immutable map: |
| if (this == EMPTY) { |
| m = new HashMap<Object,Object>(8); |
| } else { |
| m = _copy(_shared); |
| } |
| m.put(key, value); |
| return new Impl(m); |
| } |
| |
| @Override |
| public ContextAttributes withSharedAttributes(Map<?,?> shared) { |
| return new Impl(shared); |
| } |
| |
| @Override |
| public ContextAttributes withoutSharedAttribute(Object key) |
| { |
| // first couple of trivial optimizations |
| if (_shared.isEmpty()) { |
| return this; |
| } |
| if (_shared.containsKey(key)) { |
| if (_shared.size() == 1) { |
| return EMPTY; |
| } |
| } else { // if we didn't have it anyway, return as-is |
| return this; |
| } |
| // otherwise make copy, modify |
| Map<Object,Object> m = _copy(_shared); |
| m.remove(key); |
| return new Impl(m); |
| } |
| |
| /* |
| /********************************************************** |
| /* Per-call access |
| /********************************************************** |
| */ |
| |
| @Override |
| public Object getAttribute(Object key) |
| { |
| if (_nonShared != null) { |
| Object ob = _nonShared.get(key); |
| if (ob != null) { |
| if (ob == NULL_SURROGATE) { |
| return null; |
| } |
| return ob; |
| } |
| } |
| return _shared.get(key); |
| } |
| |
| @Override |
| public ContextAttributes withPerCallAttribute(Object key, Object value) |
| { |
| // First: null value may need masking |
| if (value == null) { |
| // need to mask nulls to ensure default values won't be showing |
| if (_shared.containsKey(key)) { |
| value = NULL_SURROGATE; |
| } else if ((_nonShared == null) || !_nonShared.containsKey(key)) { |
| // except if non-mutable shared list has no entry, we don't care |
| return this; |
| } else { |
| _nonShared.remove(key); |
| return this; |
| } |
| } |
| // a special case: create non-shared instance if need be |
| if (_nonShared == null) { |
| return nonSharedInstance(key, value); |
| } |
| _nonShared.put(key, value); |
| return this; |
| } |
| |
| /* |
| /********************************************************** |
| /* Internal methods |
| /********************************************************** |
| */ |
| |
| /** |
| * Overridable method that creates initial non-shared instance, |
| * with the first explicit set value. |
| */ |
| protected ContextAttributes nonSharedInstance(Object key, Object value) |
| { |
| Map<Object,Object> m = new HashMap<Object,Object>(); |
| if (value == null) { |
| value = NULL_SURROGATE; |
| } |
| m.put(key, value); |
| return new Impl(_shared, m); |
| } |
| |
| private Map<Object,Object> _copy(Map<?,?> src) |
| { |
| return new HashMap<Object,Object>(src); |
| } |
| } |
| } |