blob: a0170b93028c074a15cc4d29eed6c3b088c61871 [file] [log] [blame]
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);
}
}
}