| package org.codehaus.jackson.map.deser.impl; |
| |
| import java.io.IOException; |
| import java.util.*; |
| |
| import org.codehaus.jackson.JsonParser; |
| import org.codehaus.jackson.JsonProcessingException; |
| import org.codehaus.jackson.map.DeserializationContext; |
| import org.codehaus.jackson.map.deser.SettableBeanProperty; |
| import org.codehaus.jackson.util.TokenBuffer; |
| |
| /** |
| * Helper class that is used to flatten JSON structure when using |
| * "external type id" (see {@link org.codehaus.jackson.annotate.JsonTypeInfo.As#EXTERNAL_PROPERTY}). |
| * This is needed to store temporary state and buffer tokens, as the structure is |
| * rearranged a bit so that actual type deserializer can resolve type and |
| * finalize deserialization. |
| * |
| * @since 1.9 |
| */ |
| public class ExternalTypeHandler |
| { |
| private final ExtTypedProperty[] _properties; |
| private final HashMap<String, Integer> _nameToPropertyIndex; |
| |
| private final String[] _typeIds; |
| private final TokenBuffer[] _tokens; |
| |
| protected ExternalTypeHandler(ExtTypedProperty[] properties, |
| HashMap<String, Integer> nameToPropertyIndex, |
| String[] typeIds, TokenBuffer[] tokens) |
| { |
| _properties = properties; |
| _nameToPropertyIndex = nameToPropertyIndex; |
| _typeIds = typeIds; |
| _tokens = tokens; |
| } |
| |
| protected ExternalTypeHandler(ExternalTypeHandler h) |
| { |
| _properties = h._properties; |
| _nameToPropertyIndex = h._nameToPropertyIndex; |
| int len = _properties.length; |
| _typeIds = new String[len]; |
| _tokens = new TokenBuffer[len]; |
| } |
| |
| public ExternalTypeHandler start() { |
| return new ExternalTypeHandler(this); |
| } |
| |
| /** |
| * Method called to see if given property/value pair is an external type |
| * id; and if so handle it. This is <b>only</b> to be called in case |
| * containing POJO has similarly named property as the external type id; |
| * otherwise {@link #handleToken} should be called instead. |
| */ |
| public boolean handleTypePropertyValue(JsonParser jp, DeserializationContext ctxt, |
| String propName, Object bean) |
| throws IOException, JsonProcessingException |
| { |
| Integer I = _nameToPropertyIndex.get(propName); |
| if (I == null) { |
| return false; |
| } |
| int index = I.intValue(); |
| ExtTypedProperty prop = _properties[index]; |
| if (!prop.hasTypePropertyName(propName)) { |
| return false; |
| } |
| _typeIds[index] = jp.getText(); |
| // note: can NOT skip child values (should always be String anyway) |
| boolean canDeserialize = (bean != null) && (_tokens[index] != null); |
| // Minor optimization: deserialize properties as soon as we have all we need: |
| if (canDeserialize) { |
| _deserialize(jp, ctxt, bean, index); |
| // clear stored data, to avoid deserializing+setting twice: |
| _typeIds[index] = null; |
| _tokens[index] = null; |
| } |
| return true; |
| } |
| |
| /** |
| * Method called to ask handler to handle |
| */ |
| public boolean handleToken(JsonParser jp, DeserializationContext ctxt, |
| String propName, Object bean) |
| throws IOException, JsonProcessingException |
| { |
| Integer I = _nameToPropertyIndex.get(propName); |
| if (I == null) { |
| return false; |
| } |
| int index = I.intValue(); |
| ExtTypedProperty prop = _properties[index]; |
| boolean canDeserialize; |
| if (prop.hasTypePropertyName(propName)) { |
| _typeIds[index] = jp.getText(); |
| jp.skipChildren(); |
| canDeserialize = (bean != null) && (_tokens[index] != null); |
| } else { |
| TokenBuffer tokens = new TokenBuffer(jp.getCodec()); |
| tokens.copyCurrentStructure(jp); |
| _tokens[index] = tokens; |
| canDeserialize = (bean != null) && (_typeIds[index] != null); |
| } |
| /* Minor optimization: let's deserialize properties as soon as |
| * we have all pertinent information: |
| */ |
| if (canDeserialize) { |
| _deserialize(jp, ctxt, bean, index); |
| // clear stored data, to avoid deserializing+setting twice: |
| _typeIds[index] = null; |
| _tokens[index] = null; |
| } |
| return true; |
| } |
| |
| public Object complete(JsonParser jp, DeserializationContext ctxt, Object bean) |
| throws IOException, JsonProcessingException |
| { |
| for (int i = 0, len = _properties.length; i < len; ++i) { |
| if (_typeIds[i] == null) { |
| // let's allow missing both type and property (may already have been set, too) |
| if (_tokens[i] == null) { |
| continue; |
| } |
| // but not just one |
| throw ctxt.mappingException("Missing external type id property '"+_properties[i].getTypePropertyName()+"'"); |
| } else if (_tokens[i] == null) { |
| SettableBeanProperty prop = _properties[i].getProperty(); |
| throw ctxt.mappingException("Missing property '"+prop.getName()+"' for external type id '"+_properties[i].getTypePropertyName()); |
| } |
| _deserialize(jp, ctxt, bean, i); |
| } |
| return bean; |
| } |
| |
| protected final void _deserialize(JsonParser jp, DeserializationContext ctxt, Object bean, int index) |
| throws IOException, JsonProcessingException |
| { |
| /* Ok: time to mix type id, value; and we will actually use "wrapper-array" |
| * style to ensure we can handle all kinds of JSON constructs. |
| */ |
| TokenBuffer merged = new TokenBuffer(jp.getCodec()); |
| merged.writeStartArray(); |
| merged.writeString(_typeIds[index]); |
| JsonParser p2 = _tokens[index].asParser(jp); |
| p2.nextToken(); |
| merged.copyCurrentStructure(p2); |
| merged.writeEndArray(); |
| // needs to point to START_OBJECT (or whatever first token is) |
| p2 = merged.asParser(jp); |
| p2.nextToken(); |
| _properties[index].getProperty().deserializeAndSet(p2, ctxt, bean); |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper classes |
| /********************************************************** |
| */ |
| |
| public static class Builder |
| { |
| private final ArrayList<ExtTypedProperty> _properties = new ArrayList<ExtTypedProperty>(); |
| private final HashMap<String, Integer> _nameToPropertyIndex = new HashMap<String, Integer>(); |
| |
| public void addExternal(SettableBeanProperty property, String extPropName) |
| { |
| Integer index = _properties.size(); |
| _properties.add(new ExtTypedProperty(property, extPropName)); |
| _nameToPropertyIndex.put(property.getName(), index); |
| _nameToPropertyIndex.put(extPropName, index); |
| } |
| |
| public ExternalTypeHandler build() { |
| return new ExternalTypeHandler(_properties.toArray(new ExtTypedProperty[_properties.size()]), |
| _nameToPropertyIndex, null, null); |
| } |
| } |
| |
| private final static class ExtTypedProperty |
| { |
| private final SettableBeanProperty _property; |
| private final String _typePropertyName; |
| |
| public ExtTypedProperty(SettableBeanProperty property, String typePropertyName) |
| { |
| _property = property; |
| _typePropertyName = typePropertyName; |
| } |
| |
| public boolean hasTypePropertyName(String n) { |
| return n.equals(_typePropertyName); |
| } |
| |
| public String getTypePropertyName() { return _typePropertyName; } |
| |
| public SettableBeanProperty getProperty() { |
| return _property; |
| } |
| } |
| } |