1 package com.fasterxml.jackson.databind.deser.impl;
2
3 import java.io.IOException;
4 import java.lang.reflect.InvocationTargetException;
5 import java.util.*;
6
7 import com.fasterxml.jackson.core.JsonParser;
8 import com.fasterxml.jackson.core.JsonProcessingException;
9 import com.fasterxml.jackson.databind.DeserializationContext;
10 import com.fasterxml.jackson.databind.DeserializationFeature;
11 import com.fasterxml.jackson.databind.JsonDeserializer;
12 import com.fasterxml.jackson.databind.JsonMappingException;
13 import com.fasterxml.jackson.databind.MapperFeature;
14 import com.fasterxml.jackson.databind.PropertyName;
15 import com.fasterxml.jackson.databind.cfg.MapperConfig;
16 import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
17 import com.fasterxml.jackson.databind.util.ClassUtil;
18 import com.fasterxml.jackson.databind.util.NameTransformer;
19
20
31 public class BeanPropertyMap
32 implements Iterable<SettableBeanProperty>,
33 java.io.Serializable
34 {
35 private static final long serialVersionUID = 2L;
36
37
40 protected final boolean _caseInsensitive;
41
42 private int _hashMask;
43
44
47 private int _size;
48
49 private int _spillCount;
50
51
54 private Object[] _hashArea;
55
56
60 private final SettableBeanProperty[] _propsInOrder;
61
62
72 private final Map<String,List<PropertyName>> _aliasDefs;
73
74
79 private final Map<String,String> _aliasMapping;
80
81
88 private final Locale _locale;
89
90
93 public BeanPropertyMap(boolean caseInsensitive, Collection<SettableBeanProperty> props,
94 Map<String,List<PropertyName>> aliasDefs,
95 Locale locale)
96 {
97 _caseInsensitive = caseInsensitive;
98 _propsInOrder = props.toArray(new SettableBeanProperty[props.size()]);
99 _aliasDefs = aliasDefs;
100 _locale = locale;
101 _aliasMapping = _buildAliasMapping(aliasDefs, caseInsensitive, locale);
102 init(props);
103
104 }
105
106
109 @Deprecated
110 public BeanPropertyMap(boolean caseInsensitive, Collection<SettableBeanProperty> props,
111 Map<String,List<PropertyName>> aliasDefs) {
112 this(caseInsensitive, props, aliasDefs, Locale.getDefault());
113 }
114
115
119 private BeanPropertyMap(BeanPropertyMap src,
120 SettableBeanProperty newProp, int hashIndex, int orderedIndex)
121 {
122
123 _caseInsensitive = src._caseInsensitive;
124 _locale = src._locale;
125 _hashMask = src._hashMask;
126 _size = src._size;
127 _spillCount = src._spillCount;
128 _aliasDefs = src._aliasDefs;
129 _aliasMapping = src._aliasMapping;
130
131
132 _hashArea = Arrays.copyOf(src._hashArea, src._hashArea.length);
133 _propsInOrder = Arrays.copyOf(src._propsInOrder, src._propsInOrder.length);
134 _hashArea[hashIndex] = newProp;
135 _propsInOrder[orderedIndex] = newProp;
136 }
137
138
142 private BeanPropertyMap(BeanPropertyMap src,
143 SettableBeanProperty newProp, String key, int slot)
144 {
145
146 _caseInsensitive = src._caseInsensitive;
147 _locale = src._locale;
148 _hashMask = src._hashMask;
149 _size = src._size;
150 _spillCount = src._spillCount;
151 _aliasDefs = src._aliasDefs;
152 _aliasMapping = src._aliasMapping;
153
154
155 _hashArea = Arrays.copyOf(src._hashArea, src._hashArea.length);
156 int last = src._propsInOrder.length;
157
158 _propsInOrder = Arrays.copyOf(src._propsInOrder, last+1);
159 _propsInOrder[last] = newProp;
160
161 final int hashSize = _hashMask+1;
162 int ix = (slot<<1);
163
164
165 if (_hashArea[ix] != null) {
166
167 ix = (hashSize + (slot >> 1)) << 1;
168 if (_hashArea[ix] != null) {
169
170 ix = ((hashSize + (hashSize >> 1) ) << 1) + _spillCount;
171 _spillCount += 2;
172 if (ix >= _hashArea.length) {
173 _hashArea = Arrays.copyOf(_hashArea, _hashArea.length + 4);
174 }
175 }
176 }
177 _hashArea[ix] = key;
178 _hashArea[ix+1] = newProp;
179 }
180
181 @Deprecated
182 public BeanPropertyMap(boolean caseInsensitive, Collection<SettableBeanProperty> props)
183 {
184 this(caseInsensitive, props, Collections.<String,List<PropertyName>>emptyMap(),
185 Locale.getDefault());
186 }
187
188
191 protected BeanPropertyMap(BeanPropertyMap base, boolean caseInsensitive)
192 {
193 _caseInsensitive = caseInsensitive;
194 _locale = base._locale;
195 _aliasDefs = base._aliasDefs;
196 _aliasMapping = base._aliasMapping;
197
198
199 _propsInOrder = Arrays.copyOf(base._propsInOrder, base._propsInOrder.length);
200 init(Arrays.asList(_propsInOrder));
201 }
202
203
210 public BeanPropertyMap withCaseInsensitivity(boolean state) {
211 if (_caseInsensitive == state) {
212 return this;
213 }
214 return new BeanPropertyMap(this, state);
215 }
216
217 protected void init(Collection<SettableBeanProperty> props)
218 {
219 _size = props.size();
220
221
222 final int hashSize = findSize(_size);
223 _hashMask = hashSize-1;
224
225
226 int alloc = (hashSize + (hashSize>>1)) * 2;
227 Object[] hashed = new Object[alloc];
228 int spillCount = 0;
229
230 for (SettableBeanProperty prop : props) {
231
232 if (prop == null) {
233 continue;
234 }
235
236 String key = getPropertyName(prop);
237 int slot = _hashCode(key);
238 int ix = (slot<<1);
239
240
241 if (hashed[ix] != null) {
242
243 ix = (hashSize + (slot >> 1)) << 1;
244 if (hashed[ix] != null) {
245
246 ix = ((hashSize + (hashSize >> 1) ) << 1) + spillCount;
247 spillCount += 2;
248 if (ix >= hashed.length) {
249 hashed = Arrays.copyOf(hashed, hashed.length + 4);
250 }
251 }
252 }
253 hashed[ix] = key;
254 hashed[ix+1] = prop;
255
256
257 }
258 _hashArea = hashed;
259 _spillCount = spillCount;
260 }
261
262 private final static int findSize(int size)
263 {
264 if (size <= 5) {
265 return 8;
266 }
267 if (size <= 12) {
268 return 16;
269 }
270 int needed = size + (size >> 2);
271 int result = 32;
272 while (result < needed) {
273 result += result;
274 }
275 return result;
276 }
277
278
281 public static BeanPropertyMap construct(MapperConfig<?> config,
282 Collection<SettableBeanProperty> props,
283 Map<String,List<PropertyName>> aliasMapping) {
284 return new BeanPropertyMap(config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES),
285 props, aliasMapping,
286 config.getLocale());
287 }
288
289
292 @Deprecated
293 public static BeanPropertyMap construct(Collection<SettableBeanProperty> props,
294 boolean caseInsensitive, Map<String,List<PropertyName>> aliasMapping) {
295 return new BeanPropertyMap(caseInsensitive, props, aliasMapping);
296 }
297
298 @Deprecated
299 public static BeanPropertyMap construct(Collection<SettableBeanProperty> props, boolean caseInsensitive) {
300 return construct(props, caseInsensitive,
301 Collections.<String,List<PropertyName>>emptyMap());
302 }
303
304
311 public BeanPropertyMap withProperty(SettableBeanProperty newProp)
312 {
313
314 String key = getPropertyName(newProp);
315
316 for (int i = 1, end = _hashArea.length; i < end; i += 2) {
317 SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
318 if ((prop != null) && prop.getName().equals(key)) {
319 return new BeanPropertyMap(this, newProp, i, _findFromOrdered(prop));
320 }
321 }
322
323 final int slot = _hashCode(key);
324
325 return new BeanPropertyMap(this, newProp, key, slot);
326 }
327
328 public BeanPropertyMap assignIndexes()
329 {
330
331 int index = 0;
332 for (int i = 1, end = _hashArea.length; i < end; i += 2) {
333 SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
334 if (prop != null) {
335 prop.assignIndex(index++);
336 }
337 }
338 return this;
339 }
340
341
345 public BeanPropertyMap renameAll(NameTransformer transformer)
346 {
347 if (transformer == null || (transformer == NameTransformer.NOP)) {
348 return this;
349 }
350
351 final int len = _propsInOrder.length;
352 ArrayList<SettableBeanProperty> newProps = new ArrayList<SettableBeanProperty>(len);
353
354 for (int i = 0; i < len; ++i) {
355 SettableBeanProperty prop = _propsInOrder[i];
356
357
358 if (prop == null) {
359 newProps.add(prop);
360 continue;
361 }
362 newProps.add(_rename(prop, transformer));
363 }
364
365
366 return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs, _locale);
367 }
368
369
374
375
382 public BeanPropertyMap withoutProperties(Collection<String> toExclude)
383 {
384 if (toExclude.isEmpty()) {
385 return this;
386 }
387 final int len = _propsInOrder.length;
388 ArrayList<SettableBeanProperty> newProps = new ArrayList<SettableBeanProperty>(len);
389
390 for (int i = 0; i < len; ++i) {
391 SettableBeanProperty prop = _propsInOrder[i];
392
393
394
395 if (prop != null) {
396 if (!toExclude.contains(prop.getName())) {
397 newProps.add(prop);
398 }
399 }
400 }
401
402 return new BeanPropertyMap(_caseInsensitive, newProps, _aliasDefs, _locale);
403 }
404
405 @Deprecated
406 public void replace(SettableBeanProperty newProp)
407 {
408 String key = getPropertyName(newProp);
409 int ix = _findIndexInHash(key);
410 if (ix < 0) {
411 throw new NoSuchElementException("No entry '"+key+"' found, can't replace");
412 }
413 SettableBeanProperty prop = (SettableBeanProperty) _hashArea[ix];
414 _hashArea[ix] = newProp;
415
416 _propsInOrder[_findFromOrdered(prop)] = newProp;
417 }
418
419
426 public void replace(SettableBeanProperty origProp, SettableBeanProperty newProp)
427 {
428 int i = 1;
429 int end = _hashArea.length;
430
431 for (;; i += 2) {
432 if (i > end) {
433 throw new NoSuchElementException("No entry '"+origProp.getName()+"' found, can't replace");
434 }
435 if (_hashArea[i] == origProp) {
436 _hashArea[i] = newProp;
437 break;
438 }
439 }
440 _propsInOrder[_findFromOrdered(origProp)] = newProp;
441 }
442
443
447 public void remove(SettableBeanProperty propToRm)
448 {
449 ArrayList<SettableBeanProperty> props = new ArrayList<SettableBeanProperty>(_size);
450 String key = getPropertyName(propToRm);
451 boolean found = false;
452
453 for (int i = 1, end = _hashArea.length; i < end; i += 2) {
454 SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
455 if (prop == null) {
456 continue;
457 }
458 if (!found) {
459
460
461 found = key.equals(_hashArea[i-1]);
462 if (found) {
463
464 _propsInOrder[_findFromOrdered(prop)] = null;
465 continue;
466 }
467 }
468 props.add(prop);
469 }
470 if (!found) {
471 throw new NoSuchElementException("No entry '"+propToRm.getName()+"' found, can't remove");
472 }
473 init(props);
474 }
475
476
481
482 public int size() { return _size; }
483
484
487 public boolean isCaseInsensitive() {
488 return _caseInsensitive;
489 }
490
491
494 public boolean hasAliases() {
495 return !_aliasDefs.isEmpty();
496 }
497
498
501 @Override
502 public Iterator<SettableBeanProperty> iterator() {
503 return _properties().iterator();
504 }
505
506 private List<SettableBeanProperty> _properties() {
507 ArrayList<SettableBeanProperty> p = new ArrayList<SettableBeanProperty>(_size);
508 for (int i = 1, end = _hashArea.length; i < end; i += 2) {
509 SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
510 if (prop != null) {
511 p.add(prop);
512 }
513 }
514 return p;
515 }
516
517
525 public SettableBeanProperty[] getPropertiesInInsertionOrder() {
526 return _propsInOrder;
527 }
528
529
530
531 protected final String getPropertyName(SettableBeanProperty prop) {
532 return _caseInsensitive ? prop.getName().toLowerCase(_locale) : prop.getName();
533 }
534
535
540
541
544 public SettableBeanProperty find(int index)
545 {
546
547
548 for (int i = 1, end = _hashArea.length; i < end; i += 2) {
549 SettableBeanProperty prop = (SettableBeanProperty) _hashArea[i];
550 if ((prop != null) && (index == prop.getPropertyIndex())) {
551 return prop;
552 }
553 }
554 return null;
555 }
556
557 public SettableBeanProperty find(String key)
558 {
559 if (key == null) {
560 throw new IllegalArgumentException("Cannot pass null property name");
561 }
562 if (_caseInsensitive) {
563 key = key.toLowerCase(_locale);
564 }
565
566
567 int slot = key.hashCode() & _hashMask;
568
569
570
571 int ix = (slot<<1);
572 Object match = _hashArea[ix];
573 if ((match == key) || key.equals(match)) {
574 return (SettableBeanProperty) _hashArea[ix+1];
575 }
576 return _find2(key, slot, match);
577 }
578
579 private final SettableBeanProperty _find2(String key, int slot, Object match)
580 {
581 if (match == null) {
582
583 return _findWithAlias(_aliasMapping.get(key));
584 }
585
586 int hashSize = _hashMask+1;
587 int ix = hashSize + (slot>>1) << 1;
588 match = _hashArea[ix];
589 if (key.equals(match)) {
590 return (SettableBeanProperty) _hashArea[ix+1];
591 }
592 if (match != null) {
593 int i = (hashSize + (hashSize>>1)) << 1;
594 for (int end = i + _spillCount; i < end; i += 2) {
595 match = _hashArea[i];
596 if ((match == key) || key.equals(match)) {
597 return (SettableBeanProperty) _hashArea[i+1];
598 }
599 }
600 }
601
602 return _findWithAlias(_aliasMapping.get(key));
603 }
604
605 private SettableBeanProperty _findWithAlias(String keyFromAlias)
606 {
607 if (keyFromAlias == null) {
608 return null;
609 }
610
611
612 int slot = _hashCode(keyFromAlias);
613 int ix = (slot<<1);
614 Object match = _hashArea[ix];
615 if (keyFromAlias.equals(match)) {
616 return (SettableBeanProperty) _hashArea[ix+1];
617 }
618 if (match == null) {
619 return null;
620 }
621 return _find2ViaAlias(keyFromAlias, slot, match);
622 }
623
624 private SettableBeanProperty _find2ViaAlias(String key, int slot, Object match)
625 {
626
627 int hashSize = _hashMask+1;
628 int ix = hashSize + (slot>>1) << 1;
629 match = _hashArea[ix];
630 if (key.equals(match)) {
631 return (SettableBeanProperty) _hashArea[ix+1];
632 }
633 if (match != null) {
634 int i = (hashSize + (hashSize>>1)) << 1;
635 for (int end = i + _spillCount; i < end; i += 2) {
636 match = _hashArea[i];
637 if ((match == key) || key.equals(match)) {
638 return (SettableBeanProperty) _hashArea[i+1];
639 }
640 }
641 }
642 return null;
643 }
644
645
650
651
660 public boolean findDeserializeAndSet(JsonParser p, DeserializationContext ctxt,
661 Object bean, String key) throws IOException
662 {
663 final SettableBeanProperty prop = find(key);
664 if (prop == null) {
665 return false;
666 }
667 try {
668 prop.deserializeAndSet(p, ctxt, bean);
669 } catch (Exception e) {
670 wrapAndThrow(e, bean, key, ctxt);
671 }
672 return true;
673 }
674
675
680
681 @Override
682 public String toString()
683 {
684 StringBuilder sb = new StringBuilder();
685 sb.append("Properties=[");
686 int count = 0;
687
688 Iterator<SettableBeanProperty> it = iterator();
689 while (it.hasNext()) {
690 SettableBeanProperty prop = it.next();
691 if (count++ > 0) {
692 sb.append(", ");
693 }
694 sb.append(prop.getName());
695 sb.append('(');
696 sb.append(prop.getType());
697 sb.append(')');
698 }
699 sb.append(']');
700 if (!_aliasDefs.isEmpty()) {
701 sb.append("(aliases: ");
702 sb.append(_aliasDefs);
703 sb.append(")");
704 }
705 return sb.toString();
706 }
707
708
713
714 protected SettableBeanProperty _rename(SettableBeanProperty prop, NameTransformer xf)
715 {
716 if (prop == null) {
717 return prop;
718 }
719 String newName = xf.transform(prop.getName());
720 prop = prop.withSimpleName(newName);
721 JsonDeserializer<?> deser = prop.getValueDeserializer();
722 if (deser != null) {
723 @SuppressWarnings("unchecked")
724 JsonDeserializer<Object> newDeser = (JsonDeserializer<Object>)
725 deser.unwrappingDeserializer(xf);
726 if (newDeser != deser) {
727 prop = prop.withValueDeserializer(newDeser);
728 }
729 }
730 return prop;
731 }
732
733 protected void wrapAndThrow(Throwable t, Object bean, String fieldName, DeserializationContext ctxt)
734 throws IOException
735 {
736
737 while (t instanceof InvocationTargetException && t.getCause() != null) {
738 t = t.getCause();
739 }
740
741 ClassUtil.throwIfError(t);
742
743 boolean wrap = (ctxt == null) || ctxt.isEnabled(DeserializationFeature.WRAP_EXCEPTIONS);
744
745 if (t instanceof IOException) {
746 if (!wrap || !(t instanceof JsonProcessingException)) {
747 throw (IOException) t;
748 }
749 } else if (!wrap) {
750 ClassUtil.throwIfRTE(t);
751 }
752 throw JsonMappingException.wrapWithPath(t, bean, fieldName);
753 }
754
755
763 private final int _findIndexInHash(String key)
764 {
765 final int slot = _hashCode(key);
766 int ix = (slot<<1);
767
768
769 if (key.equals(_hashArea[ix])) {
770 return ix+1;
771 }
772
773 int hashSize = _hashMask+1;
774 ix = hashSize + (slot>>1) << 1;
775 if (key.equals(_hashArea[ix])) {
776 return ix+1;
777 }
778
779 int i = (hashSize + (hashSize>>1)) << 1;
780 for (int end = i + _spillCount; i < end; i += 2) {
781 if (key.equals(_hashArea[i])) {
782 return i+1;
783 }
784 }
785 return -1;
786 }
787
788 private final int _findFromOrdered(SettableBeanProperty prop) {
789 for (int i = 0, end = _propsInOrder.length; i < end; ++i) {
790 if (_propsInOrder[i] == prop) {
791 return i;
792 }
793 }
794 throw new IllegalStateException("Illegal state: property '"+prop.getName()+"' missing from _propsInOrder");
795 }
796
797
798 private final int _hashCode(String key) {
799
800
801
802
803
804
808 return key.hashCode() & _hashMask;
809 }
810
811
812 private Map<String,String> _buildAliasMapping(Map<String,List<PropertyName>> defs,
813 boolean caseInsensitive, Locale loc)
814 {
815 if ((defs == null) || defs.isEmpty()) {
816 return Collections.emptyMap();
817 }
818 Map<String,String> aliases = new HashMap<>();
819 for (Map.Entry<String,List<PropertyName>> entry : defs.entrySet()) {
820 String key = entry.getKey();
821 if (caseInsensitive) {
822 key = key.toLowerCase(loc);
823 }
824 for (PropertyName pn : entry.getValue()) {
825 String mapped = pn.getSimpleName();
826 if (caseInsensitive) {
827 mapped = mapped.toLowerCase(loc);
828 }
829 aliases.put(mapped, key);
830 }
831 }
832 return aliases;
833 }
834 }
835