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<
Object,
Object>
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<
Object,
Object>
_shared;
/**
* Per-call attributes that we can directly modify, since they are not
* shared between threads.
*/
protected transient
Map<
Object,
Object>
_nonShared;
/*
/**********************************************************
/* Construction, factory methods
/**********************************************************
*/
protected
Impl(
Map<
Object,
Object>
shared) {
_shared =
shared;
_nonShared = null;
}
protected
Impl(
Map<
Object,
Object>
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<
Object,
Object>
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 {
// except if non-mutable shared list has no entry, we don't care
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<
Object,
Object>
src)
{
return new
HashMap<
Object,
Object>(
src);
}
}
}