All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fasterxml.jackson.jr.ob.impl.ValueReaderLocator Maven / Gradle / Ivy

Go to download

Simple data-binding that builds directly on jackson-core (streaming), has no other dependencies, and provides additional builder-style content generator

The newest version!
package com.fasterxml.jackson.jr.ob.impl;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import com.fasterxml.jackson.jr.ob.JSON;
import com.fasterxml.jackson.jr.ob.api.ReaderWriterModifier;
import com.fasterxml.jackson.jr.ob.api.ReaderWriterProvider;
import com.fasterxml.jackson.jr.ob.api.ValueReader;
import com.fasterxml.jackson.jr.type.ResolvedType;
import com.fasterxml.jackson.jr.type.TypeBindings;
import com.fasterxml.jackson.jr.type.TypeResolver;

/**
 * Helper object used for efficient detection of type information relevant
 * to our conversion needs when writing out Java Objects as JSON.
 *

* Note that usage pattern is such that a single "root" instance is kept * by each {@link com.fasterxml.jackson.jr.ob.JSON} instance; and * an actual per-operation instance must be constructed by calling * {@link #perOperationInstance}: reason for this is that instances * use simple caching to handle the common case of repeating types * within JSON Arrays. */ public class ValueReaderLocator extends ValueLocatorBase { /** * While we should be able to cache all types in the active working set, * we should also avoid potential unbounded retention, since there is * often just one big instance per JVM (or at least ClassLoader). */ protected final static int MAX_CACHED_READERS = 500; /* /********************************************************************** /* Helper objects /********************************************************************** */ /** * For generic containers (Collections, Maps, arrays), we may need * this guy. */ protected final TypeResolver _typeResolver; /** * Provider for custom readers, if any; may be null. * * @since 2.10 */ protected final ReaderWriterProvider _readerProvider; /** * Provider for reader customizer, if any; may be null. * * @since 2.11 */ protected final ReaderWriterModifier _readerModifier; /* /********************************************************************** /* Caching /********************************************************************** */ /** * Set of {@link ValueReader}s that we have resolved */ protected final ConcurrentHashMap _knownReaders; /** * During resolution, some readers may be in-progress, but need to be * linked: for example, with cyclic type references. */ protected Map _incompleteReaders; /** * Object used for mutex during construction of a Bean deserializer: necessary * to avoid race conditions during handling of cyclic dependencies. */ protected final Object _readerLock; /* /********************************************************************** /* Instance configuration /********************************************************************** */ /** * Feature flags that are enabled */ protected final int _features; protected final JSONReader _readContext; /* /********************************************************************** /* Instance state, caching /********************************************************************** */ /** * Reusable lookup key; only used by per-thread instances. */ private ClassKey _key; /* /********************************************************************** /* Construction /********************************************************************** */ /** * Constructor for the blueprint instance */ protected ValueReaderLocator(ReaderWriterProvider rwp, ReaderWriterModifier rwm) { _features = 0; _readerProvider = rwp; _readerModifier = rwm; _knownReaders = new ConcurrentHashMap(10, 0.75f, 2); _typeResolver = new TypeResolver(); _readerLock = new Object(); _readContext = null; } protected ValueReaderLocator(ValueReaderLocator base, int features, JSONReader r) { _features = features; _readContext = r; _readerProvider = base._readerProvider; _readerModifier = base._readerModifier; _knownReaders = base._knownReaders; _typeResolver = base._typeResolver; _readerLock = base._readerLock; } protected ValueReaderLocator(ValueReaderLocator base, ReaderWriterProvider rwp, ReaderWriterModifier rwm) { // create new cache as there may be custom writers: _knownReaders = new ConcurrentHashMap(10, 0.75f, 2); _readerLock = new Object(); _features = base._features; _readContext = base._readContext; _readerProvider = rwp; _readerModifier = rwm; _typeResolver = base._typeResolver; } public final static ValueReaderLocator blueprint(ReaderWriterProvider rwp, ReaderWriterModifier rwm) { return new ValueReaderLocator(rwp, rwm); } public ValueReaderLocator with(ReaderWriterProvider rwp) { if (rwp == _readerProvider) { return this; } return new ValueReaderLocator(this, rwp, _readerModifier); } public ValueReaderLocator with(ReaderWriterModifier rwm) { if (rwm == _readerModifier) { return this; } return new ValueReaderLocator(this, _readerProvider, rwm); } public ValueReaderLocator perOperationInstance(JSONReader r, int features) { return new ValueReaderLocator(this, features & CACHE_FLAGS, r); } /* /********************************************************************** /* Public API, config access /********************************************************************** */ public ReaderWriterProvider readerWriterProvider() { return _readerProvider; } public ReaderWriterModifier readerWriterModifier() { return _readerModifier; } /* /********************************************************************** /* Public API, operations /********************************************************************** */ /** * Method used during deserialization to find handler for given * non-generic type: will first check for already resolved (and cached) readers * -- and return if one found -- and then if no cached reader found, create * one, cache, return. * * @param raw Type-erased type of value to find reader for * * @return ValueReader to use for given type */ public ValueReader findReader(Class raw) { ClassKey k = (_key == null) ? new ClassKey(raw, _features) : _key.with(raw, _features); ValueReader vr = _knownReaders.get(k); if (vr != null) { return vr; } vr = createReader(null, raw, raw); // 15-Jun-2016, tatu: Let's limit maximum number of readers to prevent // unbounded memory retention (at least wrt readers) if (_knownReaders.size() >= MAX_CACHED_READERS) { _knownReaders.clear(); } _knownReaders.putIfAbsent(new ClassKey(raw, _features), vr); return vr; } /** * Factory method for creating standard readers of any declared type. * * @param contextType Context for resolving generic type parameters * @param type Type-erased type of value to construct reader for * @param genericType Full (possibly) generic type of value to construct reader for (important * for {@link java.util.Map}, {@link java.util.Collection}). */ protected ValueReader createReader(Class contextType, Class type, Type genericType) { ValueReader r = _createReader(contextType, type, genericType); if (_readerModifier != null) { r = _readerModifier.modifyValueReader(_readContext, type, r); if (r == null) { // sanity check throw new IllegalArgumentException("ReaderWriterModifier.modifyValueReader() returned null"); } } return r; } protected ValueReader _createReader(Class contextType, Class type, Type genericType) { if (type == Object.class) { return AnyReader.std; } if (type.isArray()) { return arrayReader(contextType, type); } if (type.isEnum()) { if (_readerProvider != null) { ValueReader r = _readerProvider.findValueReader(_readContext, type); if (r != null) { return r; } } return enumReader(type); } if (Collection.class.isAssignableFrom(type)) { return collectionReader(contextType, genericType); } if (Map.class.isAssignableFrom(type)) { return mapReader(contextType, genericType); } // Unlike with other types, check custom handler here before // simple type check, to allow overriding handling of `String` etc if (_readerProvider != null) { ValueReader r = _readerProvider.findValueReader(_readContext, type); if (r != null) { return r; } } int typeId = _findSimpleType(type, false); if (typeId > 0) { return new SimpleValueReader(type, typeId); } return beanReader(type); } /* /********************************************************************** /* Factory methods for non-Bean readers /********************************************************************** */ protected ValueReader arrayReader(Class contextType, Class arrayType) { // TODO: maybe allow custom array readers? Class elemType = arrayType.getComponentType(); if (!elemType.isPrimitive()) { return new ArrayReader(arrayType, elemType, createReader(contextType, elemType, elemType)); } int typeId = _findSimpleType(arrayType, false); if (typeId > 0) { return new SimpleValueReader(arrayType, typeId); } throw new IllegalArgumentException("Deserialization of "+arrayType.getName()+" not (yet) supported"); } protected ValueReader enumReader(Class enumType) { // Call pojoDefinitionForDeserialization so that the annotation support extension can get custom names for // enum values POJODefinition def = null; if (_readerModifier != null) { def = _readerModifier.pojoDefinitionForDeserialization(_readContext, enumType); } Map byName = JSON.Feature.ACCEPT_CASE_INSENSITIVE_ENUMS.isEnabled(_features) ? new TreeMap(String.CASE_INSENSITIVE_ORDER) // Note: we might want to retain declaration order with LHM in future // for error reporting; but not needed yet : new HashMap(); Object[] enums = enumType.getEnumConstants(); if (def == null) { for (Object e : enums) { byName.put(e.toString(), e); } } else { for (POJODefinition.Prop e : def.getProperties()) { if (e.field != null && e.field.isEnumConstant()) { try { byName.put(e.name, e.field.get(null)); } catch (IllegalAccessException ex) { // Don't believe that this should be possible, but raise it up just in case throw new RuntimeException(ex); } } } } return new EnumReader(enumType, enums, byName); } protected ValueReader collectionReader(Class contextType, Type collectionType) { ResolvedType t = _typeResolver.resolve(_bindings(contextType), collectionType); List params = t.typeParametersFor(Collection.class); return collectionReader(t.erasedType(), params.get(0)); } protected ValueReader collectionReader(Class collectionType, ResolvedType valueType) { final Class rawValueType = valueType.erasedType(); final ValueReader valueReader; if (Collection.class.isAssignableFrom(rawValueType)) { List params = valueType.typeParametersFor(Collection.class); valueReader = collectionReader(rawValueType, params.get(0)); } else if (Map.class.isAssignableFrom(rawValueType)) { List params = valueType.typeParametersFor(Map.class); valueReader = mapReader(rawValueType, params.get(1)); } else { valueReader = findReader(rawValueType); } if (_readerProvider != null) { ValueReader r = _readerProvider.findCollectionReader(_readContext, collectionType, valueType, valueReader); if (r != null) { return r; } } return new CollectionReader(collectionType, valueReader); } protected ValueReader mapReader(Class contextType, Type mapType) { ResolvedType t = _typeResolver.resolve(_bindings(contextType), mapType); List params = t.typeParametersFor(Map.class); return mapReader(t.erasedType(), params.get(1)); } protected ValueReader mapReader(Class mapType, ResolvedType valueType) { final Class rawValueType = valueType.erasedType(); final ValueReader valueReader; if (Collection.class.isAssignableFrom(rawValueType)) { List params = valueType.typeParametersFor(Collection.class); valueReader = collectionReader(rawValueType, params.get(0)); } else if (Map.class.isAssignableFrom(rawValueType)) { List params = valueType.typeParametersFor(Map.class); valueReader = mapReader(rawValueType, params.get(1)); } else { valueReader = findReader(rawValueType); } if (_readerProvider != null) { ValueReader r = _readerProvider.findMapReader(_readContext, mapType, valueType, valueReader); if (r != null) { return r; } } return new MapReader(mapType, valueReader); } protected ValueReader beanReader(Class type) { // NOTE: caller (must) handle custom reader lookup earlier, not done here final ClassKey key = new ClassKey(type, _features); synchronized (_readerLock) { if (_incompleteReaders == null) { _incompleteReaders = new HashMap(); } else { // perhaps it has already been resolved? ValueReader vr = _incompleteReaders.get(key); if (vr != null) { return vr; } } final BeanReader def = _resolveBeanForDeser(type, _resolveBeanDef(type)); try { _incompleteReaders.put(key, def); for (Map.Entry entry : def.propertiesByName().entrySet()) { BeanPropertyReader prop = entry.getValue(); entry.setValue(prop.withReader(createReader(type, prop.rawSetterType(), prop.genericSetterType()))); } } finally { _incompleteReaders.remove(key); } return def; } } /* /********************************************************************** /* Internal methods /********************************************************************** */ protected POJODefinition _resolveBeanDef(Class raw) { try { if (_readerModifier != null) { POJODefinition def = _readerModifier.pojoDefinitionForDeserialization(_readContext, raw); if (def != null) { return def; } } return BeanPropertyIntrospector.instance().pojoDefinitionForDeserialization(_readContext, raw); } catch (Exception e) { throw new IllegalArgumentException(String.format ("Failed to introspect ClassDefinition for type '%s': %s", raw.getName(), e.getMessage()), e); } } protected BeanReader _resolveBeanForDeser(Class raw, POJODefinition beanDef) { final BeanConstructors constructors = beanDef.constructors(); final boolean forceAccess = JSON.Feature.FORCE_REFLECTION_ACCESS.isEnabled(_features); if (forceAccess) { constructors.forceAccess(); } final boolean caseInsensitive = JSON.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES.isEnabled(_features); final List rawProps = beanDef.getProperties(); final int len = rawProps.size(); final Map propMap; Map aliasMapping = null; boolean isRecord = RecordsHelpers.isRecordType(raw); if (len == 0) { propMap = Collections.emptyMap(); } else { Set recordProps = isRecord ? new HashSet<>(RecordsHelpers.recordPropertyNames(raw)) : null; propMap = caseInsensitive ? new TreeMap<>(String.CASE_INSENSITIVE_ORDER) // 13-May-2021, tatu: Let's retain ordering here: : new LinkedHashMap<>(); final boolean useFields = JSON.Feature.USE_FIELDS.isEnabled(_features); for (int i = 0; i < len; ++i) { POJODefinition.Prop rawProp = rawProps.get(i); Method setter = rawProp.setter; Field field = useFields ? rawProp.field : null; if (setter != null) { if (forceAccess) { setter.setAccessible(true); } else if (!Modifier.isPublic(setter.getModifiers())) { // access to non-public setters must be forced to be usable: setter = null; } } if (isRecord) { // Records can only deserialize propreties that are declared in the record; // other virtual properties (getter methods) need to be ignored if (!recordProps.contains(rawProp.name)) { continue; } try { field = raw.getDeclaredField(rawProp.name); } catch (NoSuchFieldException e) { throw new IllegalStateException("Cannot access field " + rawProp.name + " of record class " + raw.getName(), e); } } else { // if no setter, field would do as well if (setter == null) { if (field == null) { continue; } // fields should always be public, but let's just double-check if (forceAccess) { field.setAccessible(true); } else if (!Modifier.isPublic(field.getModifiers())) { continue; } } } propMap.put(rawProp.name, new BeanPropertyReader(rawProp.name, field, setter, i)); // 25-Jan-2020, tatu: Aliases are a bit different because we can not tie them into // specific reader instance, due to resolution of cyclic dependencies. Instead, // we must link via name of primary property, unfortunately: if (rawProp.hasAliases()) { if (aliasMapping == null) { aliasMapping = caseInsensitive ? new TreeMap<>(String.CASE_INSENSITIVE_ORDER) : new HashMap<>(); } for (String alias : rawProp.aliases()) { aliasMapping.put(alias, rawProp.name); } } } } return new BeanReader(raw, propMap, constructors, beanDef.getIgnorableNames(), aliasMapping); } private TypeBindings _bindings(Class ctxt) { if (ctxt == null) { return TypeBindings.emptyBindings(); } return TypeBindings.create(ctxt, (ResolvedType[]) null); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy