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); } } }