1 package com.fasterxml.jackson.databind.ser; 2 3 import java.util.*; 4 5 import com.fasterxml.jackson.databind.*; 6 import com.fasterxml.jackson.databind.introspect.AnnotatedClass; 7 import com.fasterxml.jackson.databind.introspect.AnnotatedMember; 8 import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter; 9 10 /** 11 * Builder class used for aggregating deserialization information about 12 * a POJO, in order to build a {@link JsonSerializer} for serializing 13 * intances. 14 * Main reason for using separate builder class is that this makes it easier 15 * to make actual serializer class fully immutable. 16 */ 17 public class BeanSerializerBuilder 18 { 19 private final static BeanPropertyWriter[] NO_PROPERTIES = new BeanPropertyWriter[0]; 20 21 /* 22 /********************************************************** 23 /* Basic configuration we start with 24 /********************************************************** 25 */ 26 27 final protected BeanDescription _beanDesc; 28 29 protected SerializationConfig _config; 30 31 /* 32 /********************************************************** 33 /* Accumulated information about properties 34 /********************************************************** 35 */ 36 37 /** 38 * Bean properties, in order of serialization 39 */ 40 protected List<BeanPropertyWriter> _properties = Collections.emptyList(); 41 42 /** 43 * Optional array of filtered property writers; if null, no 44 * view-based filtering is performed. 45 */ 46 protected BeanPropertyWriter[] _filteredProperties; 47 48 /** 49 * Writer used for "any getter" properties, if any. 50 */ 51 protected AnyGetterWriter _anyGetter; 52 53 /** 54 * Id of the property filter to use for POJO, if any. 55 */ 56 protected Object _filterId; 57 58 /** 59 * Property that is used for type id (and not serialized as regular 60 * property) 61 */ 62 protected AnnotatedMember _typeId; 63 64 /** 65 * Object responsible for serializing Object Ids for the handled 66 * type, if any. 67 */ 68 protected ObjectIdWriter _objectIdWriter; 69 70 /* 71 /********************************************************** 72 /* Construction and setter methods 73 /********************************************************** 74 */ 75 BeanSerializerBuilder(BeanDescription beanDesc)76 public BeanSerializerBuilder(BeanDescription beanDesc) { 77 _beanDesc = beanDesc; 78 } 79 80 /** 81 * Copy-constructor that may be used for sub-classing 82 */ BeanSerializerBuilder(BeanSerializerBuilder src)83 protected BeanSerializerBuilder(BeanSerializerBuilder src) { 84 _beanDesc = src._beanDesc; 85 _properties = src._properties; 86 _filteredProperties = src._filteredProperties; 87 _anyGetter = src._anyGetter; 88 _filterId = src._filterId; 89 } 90 91 /** 92 * Initialization method called right after construction, to specify 93 * configuration to use. 94 *<p> 95 * Note: ideally should be passed in constructor, but for backwards 96 * compatibility, needed to add a setter instead 97 * 98 * @since 2.1 99 */ setConfig(SerializationConfig config)100 protected void setConfig(SerializationConfig config) { 101 _config = config; 102 } 103 setProperties(List<BeanPropertyWriter> properties)104 public void setProperties(List<BeanPropertyWriter> properties) { 105 _properties = properties; 106 } 107 108 /** 109 * @param properties Number and order of properties here MUST match that 110 * of "regular" properties set earlier using {@link #setProperties(List)}; if not, 111 * an {@link IllegalArgumentException} will be thrown 112 */ setFilteredProperties(BeanPropertyWriter[] properties)113 public void setFilteredProperties(BeanPropertyWriter[] properties) { 114 if (properties != null) { 115 if (properties.length != _properties.size()) { // as per [databind#1612] 116 throw new IllegalArgumentException(String.format( 117 "Trying to set %d filtered properties; must match length of non-filtered `properties` (%d)", 118 properties.length, _properties.size())); 119 } 120 } 121 _filteredProperties = properties; 122 } 123 setAnyGetter(AnyGetterWriter anyGetter)124 public void setAnyGetter(AnyGetterWriter anyGetter) { 125 _anyGetter = anyGetter; 126 } 127 setFilterId(Object filterId)128 public void setFilterId(Object filterId) { 129 _filterId = filterId; 130 } 131 setTypeId(AnnotatedMember idProp)132 public void setTypeId(AnnotatedMember idProp) { 133 // Not legal to use multiple ones... 134 if (_typeId != null) { 135 throw new IllegalArgumentException("Multiple type ids specified with "+_typeId+" and "+idProp); 136 } 137 _typeId = idProp; 138 } 139 setObjectIdWriter(ObjectIdWriter w)140 public void setObjectIdWriter(ObjectIdWriter w) { 141 _objectIdWriter = w; 142 } 143 144 /* 145 /********************************************************** 146 /* Accessors for things BeanSerializer cares about: 147 /* note -- likely to change between minor revisions 148 /* by new methods getting added. 149 /********************************************************** 150 */ 151 getClassInfo()152 public AnnotatedClass getClassInfo() { return _beanDesc.getClassInfo(); } 153 getBeanDescription()154 public BeanDescription getBeanDescription() { return _beanDesc; } 155 getProperties()156 public List<BeanPropertyWriter> getProperties() { return _properties; } hasProperties()157 public boolean hasProperties() { 158 return (_properties != null) && (_properties.size() > 0); 159 } 160 getFilteredProperties()161 public BeanPropertyWriter[] getFilteredProperties() { return _filteredProperties; } 162 getAnyGetter()163 public AnyGetterWriter getAnyGetter() { return _anyGetter; } 164 getFilterId()165 public Object getFilterId() { return _filterId; } 166 getTypeId()167 public AnnotatedMember getTypeId() { return _typeId; } 168 getObjectIdWriter()169 public ObjectIdWriter getObjectIdWriter() { return _objectIdWriter; } 170 171 /* 172 /********************************************************** 173 /* Build methods for actually creating serializer instance 174 /********************************************************** 175 */ 176 177 /** 178 * Method called to create {@link BeanSerializer} instance with 179 * all accumulated information. Will construct a serializer if we 180 * have enough information, or return null if not. 181 */ build()182 public JsonSerializer<?> build() 183 { 184 // [databind#2789]: There can be a case wherein `_typeId` is used, but 185 // nothing else. Rare but has happened; so force access. 186 if (_typeId != null) { 187 if (_config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) { 188 _typeId.fixAccess(_config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); 189 } 190 } 191 if (_anyGetter != null) { 192 _anyGetter.fixAccess(_config); 193 } 194 195 BeanPropertyWriter[] properties; 196 // No properties, any getter or object id writer? 197 // No real serializer; caller gets to handle 198 if (_properties == null || _properties.isEmpty()) { 199 if (_anyGetter == null && _objectIdWriter == null) { 200 // NOTE! Caller may still call `createDummy()` later on 201 return null; 202 } 203 properties = NO_PROPERTIES; 204 } else { 205 properties = _properties.toArray(new BeanPropertyWriter[_properties.size()]); 206 if (_config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) { 207 for (int i = 0, end = properties.length; i < end; ++i) { 208 properties[i].fixAccess(_config); 209 } 210 } 211 } 212 // 27-Apr-2017, tatu: Verify that filtered-properties settings are compatible 213 if (_filteredProperties != null) { 214 if (_filteredProperties.length != _properties.size()) { 215 throw new IllegalStateException(String.format( 216 "Mismatch between `properties` size (%d), `filteredProperties` (%s): should have as many (or `null` for latter)", 217 _properties.size(), _filteredProperties.length)); 218 } 219 } 220 return new BeanSerializer(_beanDesc.getType(), this, 221 properties, _filteredProperties); 222 } 223 224 /** 225 * Factory method for constructing an "empty" serializer; one that 226 * outputs no properties (but handles JSON objects properly, including 227 * type information) 228 */ createDummy()229 public BeanSerializer createDummy() { 230 // 20-Sep-2019, tatu: Can not skimp on passing builder (see [databind#2077]) 231 return BeanSerializer.createDummy(_beanDesc.getType(), this); 232 } 233 } 234 235