Fixes #528 - Add support for As.EXISTING_PROPERTY inclusion mechanism
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeDeserializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeDeserializer.java
new file mode 100644
index 0000000..f075233
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeDeserializer.java
@@ -0,0 +1,156 @@
+package com.fasterxml.jackson.databind.jsontype.impl;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.util.JsonParserSequence;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
+import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
+import com.fasterxml.jackson.databind.util.TokenBuffer;
+
+/**
+ * Type deserializer used with {@link As#EXISTING_PROPERTY}
+ * inclusion mechanism.
+ * 
+ * @author fleeman (modeled after code by tatus)
+ */
+public class AsExistingPropertyTypeDeserializer extends AsArrayTypeDeserializer
+{
+    private static final long serialVersionUID = 1L;
+
+    public AsExistingPropertyTypeDeserializer(JavaType bt, TypeIdResolver idRes,
+            String typePropertyName, boolean typeIdVisible, Class<?> defaultImpl)
+    {
+        super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl);
+    }
+
+    public AsExistingPropertyTypeDeserializer(AsExistingPropertyTypeDeserializer src, BeanProperty property) {
+        super(src, property);
+    }
+    
+    @Override
+    public TypeDeserializer forProperty(BeanProperty prop) {
+        return (prop == _property) ? this : new AsExistingPropertyTypeDeserializer(this, prop);
+    }
+    
+    @Override
+    public As getTypeInclusion() { return As.EXISTING_PROPERTY; }
+
+    /**
+     * This is the trickiest thing to handle, since property we are looking
+     * for may be anywhere...
+     */
+    @Override
+    public Object deserializeTypedFromObject(JsonParser jp, DeserializationContext ctxt) throws IOException
+    {
+        // 02-Aug-2013, tatu: May need to use native type ids
+        if (jp.canReadTypeId()) {
+            Object typeId = jp.getTypeId();
+            if (typeId != null) {
+                return _deserializeWithNativeTypeId(jp, ctxt, typeId);
+            }
+        }
+        
+        // but first, sanity check to ensure we have START_OBJECT or FIELD_NAME
+        JsonToken t = jp.getCurrentToken();
+        if (t == JsonToken.START_OBJECT) {
+            t = jp.nextToken();
+        } else if (t == JsonToken.START_ARRAY) {
+            /* This is most likely due to the fact that not all Java types are
+             * serialized as JSON Objects; so if "as-property" inclusion is requested,
+             * serialization of things like Lists must be instead handled as if
+             * "as-wrapper-array" was requested.
+             * But this can also be due to some custom handling: so, if "defaultImpl"
+             * is defined, it will be asked to handle this case.
+             */
+            return _deserializeTypedUsingDefaultImpl(jp, ctxt, null);
+        } else if (t != JsonToken.FIELD_NAME) {
+            return _deserializeTypedUsingDefaultImpl(jp, ctxt, null);
+        }
+        // Ok, let's try to find the property. But first, need token buffer...
+        TokenBuffer tb = null;
+
+        for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) {
+            String name = jp.getCurrentName();
+            jp.nextToken(); // to point to the value
+            if (_typePropertyName.equals(name)) { // gotcha!
+                return _deserializeTypedForId(jp, ctxt, tb);
+            }
+            if (tb == null) {
+                tb = new TokenBuffer(null, false);
+            }
+            tb.writeFieldName(name);
+            tb.copyCurrentStructure(jp);
+        }
+        return _deserializeTypedUsingDefaultImpl(jp, ctxt, tb);
+    }
+
+    @SuppressWarnings("resource")
+    protected final Object _deserializeTypedForId(JsonParser jp, DeserializationContext ctxt, TokenBuffer tb) throws IOException
+    {
+        String typeId = jp.getText();
+        JsonDeserializer<Object> deser = _findDeserializer(ctxt, typeId);
+        if (_typeIdVisible) { // need to merge id back in JSON input?
+            if (tb == null) {
+                tb = new TokenBuffer(null, false);
+            }
+            tb.writeFieldName(jp.getCurrentName());
+            tb.writeString(typeId);
+        }
+        if (tb != null) { // need to put back skipped properties?
+            jp = JsonParserSequence.createFlattened(tb.asParser(jp), jp);
+        }
+        // Must point to the next value; tb had no current, jp pointed to VALUE_STRING:
+        jp.nextToken(); // to skip past String value
+        // deserializer should take care of closing END_OBJECT as well
+        return deser.deserialize(jp, ctxt);
+    }
+    
+    // off-lined to keep main method lean and mean...
+    protected Object _deserializeTypedUsingDefaultImpl(JsonParser jp, DeserializationContext ctxt, TokenBuffer tb) throws IOException
+    {
+        // As per [JACKSON-614], may have default implementation to use
+        JsonDeserializer<Object> deser = _findDefaultImplDeserializer(ctxt);
+        if (deser != null) {
+            if (tb != null) {
+                tb.writeEndObject();
+                jp = tb.asParser(jp);
+                // must move to point to the first token:
+                jp.nextToken();
+            }
+            return deser.deserialize(jp, ctxt);
+        }
+        // or, perhaps we just bumped into a "natural" value (boolean/int/double/String)?
+        Object result = TypeDeserializer.deserializeIfNatural(jp, ctxt, _baseType);
+        if (result != null) {
+            return result;
+        }
+        // or, something for which "as-property" won't work, changed into "wrapper-array" type:
+        if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
+            return super.deserializeTypedFromAny(jp, ctxt);
+        }
+        throw ctxt.wrongTokenException(jp, JsonToken.FIELD_NAME,
+                "missing property '"+_typePropertyName+"' that is to contain type id  (for class "+baseTypeName()+")");
+    }
+
+    /* As per [JACKSON-352], also need to re-route "unknown" version. Need to think
+     * this through bit more in future, but for now this does address issue and has
+     * no negative side effects (at least within existing unit test suite).
+     */
+    @Override
+    public Object deserializeTypedFromAny(JsonParser jp, DeserializationContext ctxt) throws IOException {
+        /* [JACKSON-387]: Sometimes, however, we get an array wrapper; specifically
+         *   when an array or list has been serialized with type information.
+         */
+        if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
+            return super.deserializeTypedFromArray(jp, ctxt);
+        }
+        return deserializeTypedFromObject(jp, ctxt);
+    }    
+    
+    // These are fine from base class:
+    //public Object deserializeTypedFromArray(JsonParser jp, DeserializationContext ctxt)
+    //public Object deserializeTypedFromScalar(JsonParser jp, DeserializationContext ctxt)    
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeSerializer.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeSerializer.java
new file mode 100644
index 0000000..b669106
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/AsExistingPropertyTypeSerializer.java
@@ -0,0 +1,96 @@
+package com.fasterxml.jackson.databind.jsontype.impl;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.core.*;
+
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
+
+/**
+ * Type serializer used with {@link As#EXISTING_PROPERTY} inclusion mechanism.
+ * Expects type information to be a well-defined property on all sub-classes.
+ * 
+ * 10/15/2014 - At time of commit, deserialization identical to deserializer
+ * for {@link As#PROPERTY} inclusion mechanism
+ * 
+ * @author fleeman (modeled after code by tatus)
+ */
+public class AsExistingPropertyTypeSerializer
+    extends AsArrayTypeSerializer
+{
+    protected final String _typePropertyName;
+
+    public AsExistingPropertyTypeSerializer(TypeIdResolver idRes, BeanProperty property, String propName)
+    {
+        super(idRes, property);
+        _typePropertyName = propName;
+    }
+
+    @Override
+    public AsExistingPropertyTypeSerializer forProperty(BeanProperty prop) {
+        return (_property == prop) ? this : new AsExistingPropertyTypeSerializer(this._idResolver, prop, this._typePropertyName);
+    }
+    
+    @Override
+    public String getPropertyName() { return _typePropertyName; }
+
+    @Override
+    public As getTypeInclusion() { return As.EXISTING_PROPERTY; }
+    
+    @Override
+    public void writeTypePrefixForObject(Object value, JsonGenerator jgen) throws IOException
+    {
+        final String typeId = idFromValue(value);
+        if (jgen.canWriteTypeId()) {
+            jgen.writeTypeId(typeId);
+            jgen.writeStartObject();
+        } else {
+            jgen.writeStartObject();
+        }
+    }
+
+    @Override
+    public void writeTypePrefixForObject(Object value, JsonGenerator jgen, Class<?> type) throws IOException
+    {
+        final String typeId = idFromValueAndType(value, type);
+        if (jgen.canWriteTypeId()) {
+            jgen.writeTypeId(typeId);
+            jgen.writeStartObject();
+        } else {
+            jgen.writeStartObject();
+        }
+    }
+    
+    @Override
+    public void writeTypeSuffixForObject(Object value, JsonGenerator jgen) throws IOException {
+        // always need to close, regardless of whether its native type id or not
+    	jgen.writeEndObject();
+    }
+
+
+    /*
+    /**********************************************************
+    /* Writing with custom type id
+    /**********************************************************
+     */
+
+    // Only need to override Object-variants
+    
+    @Override
+    public void writeCustomTypePrefixForObject(Object value, JsonGenerator jgen, String typeId) throws IOException
+    {
+    	if (jgen.canWriteTypeId()) {
+            jgen.writeTypeId(typeId);
+            jgen.writeStartObject();
+        } else {
+            jgen.writeStartObject();
+        }
+    }
+
+    @Override
+    public void writeCustomTypeSuffixForObject(Object value, JsonGenerator jgen, String typeId) throws IOException {
+        jgen.writeEndObject();
+    }
+}
diff --git a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java
index cba4d32..1edadbe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdTypeResolverBuilder.java
@@ -84,15 +84,17 @@
             return new AsExternalTypeSerializer(idRes, null,
                     _typeProperty);
         case EXISTING_PROPERTY:
-            throw _noExisting();
+        	// as per [#528]
+        	return new AsExistingPropertyTypeSerializer(idRes, null, _typeProperty);
         }
         throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
     }
 
     // as per [#368]
-    private IllegalArgumentException _noExisting() {
-        return new IllegalArgumentException("Inclusion type "+_includeAs+" not yet supported");
-    }
+    // removed when fix [#528]
+    //private IllegalArgumentException _noExisting() {
+    //    return new IllegalArgumentException("Inclusion type "+_includeAs+" not yet supported");
+    //}
 
     @Override
     public TypeDeserializer buildTypeDeserializer(DeserializationConfig config,
@@ -117,7 +119,9 @@
             return new AsExternalTypeDeserializer(baseType, idRes,
                     _typeProperty, _typeIdVisible, _defaultImpl);
         case EXISTING_PROPERTY:
-            throw _noExisting();
+        	// as per [#528]
+        	return new AsExistingPropertyTypeDeserializer(baseType, idRes,
+                    _typeProperty, _typeIdVisible, _defaultImpl);
         }
         throw new IllegalStateException("Do not know how to construct standard type serializer for inclusion type: "+_includeAs);
     }
diff --git a/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesExistingProperty.java b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesExistingProperty.java
new file mode 100644
index 0000000..e547d2e
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/jsontype/TestSubtypesExistingProperty.java
@@ -0,0 +1,284 @@
+package com.fasterxml.jackson.databind.jsontype;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.fasterxml.jackson.databind.BaseMapTest;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class TestSubtypesExistingProperty extends BaseMapTest {
+	
+    /**
+     * Polymorphic base class - existing property forced by abstract method
+     */
+	@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type")
+	@JsonSubTypes({
+		@Type(value = Dog.class, name = "doggie") ,
+		@Type(value = Cat.class, name = "kitty") 
+		})
+	static abstract class Animal {
+        public String name;
+        
+        protected Animal(String n)  { name = n; }
+        
+        public abstract String getType();
+    }
+
+    @JsonTypeName("doggie")
+    static class Dog extends Animal
+    {
+        public int boneCount;
+        
+        private Dog() { super(null); }
+        public Dog(String name, int b) {
+            super(name);
+            boneCount = b;
+        }
+        
+ 		@Override
+		public String getType() {
+        	return "doggie";
+        }        
+    }
+    
+    @JsonTypeName("kitty")
+    static class Cat extends Animal
+    {
+        public String furColor;
+        
+        private Cat() { super(null); }
+        public Cat(String name, String c) {
+            super(name);
+            furColor = c;
+        }
+        
+		@Override
+		public String getType() {
+        	return "kitty";
+        }        
+    }
+
+    static class AnimalWrapper {
+        public Animal animal;
+        public AnimalWrapper() {}
+        public AnimalWrapper(Animal a) { animal = a; }
+    }
+
+
+    /**
+     * Polymorphic base class - existing property NOT forced by abstract method on base class
+     */
+	@JsonTypeInfo(use = Id.NAME, include = As.EXISTING_PROPERTY, property = "type")
+	@JsonSubTypes({
+		@Type(value = Accord.class, name = "accord") ,
+		@Type(value = Camry.class, name = "camry") 
+		})
+	static abstract class Car {
+        public String name;        
+        protected Car(String n)  { name = n; }
+    }
+
+    @JsonTypeName("accord")
+    static class Accord extends Car
+    {
+        public int speakerCount;
+        
+        private Accord() { super(null); }
+        public Accord(String name, int b) {
+            super(name);
+            speakerCount = b;
+        }
+        
+		public String getType() {
+        	return "accord";
+        }        
+    }
+    
+    @JsonTypeName("camry")
+    static class Camry extends Car
+    {
+        public String exteriorColor;
+        
+        private Camry() { super(null); }
+        public Camry(String name, String c) {
+            super(name);
+            exteriorColor = c;
+        }
+        
+		public String getType() {
+        	return "camry";
+        }        
+    }
+
+    static class CarWrapper {
+        public Car car;
+        public CarWrapper() {}
+        public CarWrapper(Car c) { car = c; }
+    }
+    
+    private final ObjectMapper MAPPER = new ObjectMapper();
+
+    /*
+    /**********************************************************
+    /* Mock data
+    /**********************************************************
+     */
+
+	private static final Cat beelzebub = new Cat("Beelzebub", "tabby");
+	private static final String beelzebubJson = "{\"name\":\"Beelzebub\",\"furColor\":\"tabby\",\"type\":\"kitty\"}";	
+	private static final Dog rover = new Dog("Rover", 42);
+	private static final String roverJson = "{\"name\":\"Rover\",\"boneCount\":42,\"type\":\"doggie\"}";
+	private static final AnimalWrapper beelzebubWrapper = new AnimalWrapper(beelzebub);
+	private static final String beelzebubWrapperJson = "{\"animal\":" + beelzebubJson + "}";
+	private static final List<Animal> animalList = Arrays.asList(beelzebub, rover);
+	private static final String animalListJson = "[" + beelzebubJson + "," + roverJson + "]";
+
+	private static final Camry camry = new Camry("Sweet Ride", "candy-apple-red");
+	private static final String camryJson = "{\"name\":\"Sweet Ride\",\"exteriorColor\":\"candy-apple-red\",\"type\":\"camry\"}";	
+	private static final Accord accord = new Accord("Road Rage", 6);
+	private static final String accordJson = "{\"name\":\"Road Rage\",\"speakerCount\":6,\"type\":\"accord\"}";
+	private static final CarWrapper camryWrapper = new CarWrapper(camry);
+	private static final String camryWrapperJson = "{\"car\":" + camryJson + "}";
+	private static final List<Car> carList = Arrays.asList(camry, accord);
+	private static final String carListJson = "[" + camryJson + "," + accordJson + "]";
+
+    /*
+    /**********************************************************
+    /* Unit tests
+    /**********************************************************
+     */
+
+    /**
+     * Animals - serialization tests for abstract method in base class
+     */
+    public void testExistingPropertySerializationAnimals() throws Exception
+    {
+        Map<String,Object> result = writeAndMap(MAPPER, beelzebub);
+        assertEquals(3, result.size());
+        assertEquals(beelzebub.name, result.get("name"));
+        assertEquals(beelzebub.furColor, result.get("furColor"));
+        assertEquals(beelzebub.getType(), result.get("type"));
+
+        result = writeAndMap(MAPPER, rover);
+        assertEquals(3, result.size());
+        assertEquals(rover.name, result.get("name"));
+        assertEquals(rover.boneCount, result.get("boneCount"));
+        assertEquals(rover.getType(), result.get("type"));
+        
+        String beelzebubSerialized = MAPPER.writeValueAsString(beelzebub);
+        assertEquals(beelzebubSerialized, beelzebubJson);
+        
+        String roverSerialized = MAPPER.writeValueAsString(rover);
+        assertEquals(roverSerialized, roverJson);
+        
+        String animalWrapperSerialized = MAPPER.writeValueAsString(beelzebubWrapper);
+        assertEquals(animalWrapperSerialized, beelzebubWrapperJson);
+
+        String animalListSerialized = MAPPER.writeValueAsString(animalList);
+        assertEquals(animalListSerialized, animalListJson);
+    }
+
+    /**
+     * Animals - deserialization tests for abstract method in base class
+     */
+    public void testSimpleClassAsExistingPropertyDeserializationAnimals() throws Exception
+    {
+    	Animal beelzebubDeserialized = MAPPER.readValue(beelzebubJson, Animal.class);
+    	assertTrue(beelzebubDeserialized instanceof Cat);
+        assertSame(beelzebubDeserialized.getClass(), Cat.class);
+    	assertEquals(beelzebub.name, beelzebubDeserialized.name);
+    	assertEquals(beelzebub.furColor, ((Cat) beelzebubDeserialized).furColor);
+    	assertEquals(beelzebub.getType(), beelzebubDeserialized.getType());
+
+    	AnimalWrapper beelzebubWrapperDeserialized = MAPPER.readValue(beelzebubWrapperJson, AnimalWrapper.class);
+    	Animal beelzebubExtracted = beelzebubWrapperDeserialized.animal;
+    	assertTrue(beelzebubExtracted instanceof Cat);
+        assertSame(beelzebubExtracted.getClass(), Cat.class);
+    	assertEquals(beelzebub.name, beelzebubExtracted.name);
+    	assertEquals(beelzebub.furColor, ((Cat) beelzebubExtracted).furColor);
+    	assertEquals(beelzebub.getType(), beelzebubExtracted.getType());
+    	
+    	@SuppressWarnings("unchecked")
+		List<Animal> animalListDeserialized = MAPPER.readValue(animalListJson, List.class);
+    	assertNotNull(animalListDeserialized);
+    	assertTrue(animalListDeserialized.size() == 2);
+    	Animal cat = MAPPER.convertValue(animalListDeserialized.get(0), Animal.class);
+    	assertTrue(cat instanceof Cat);
+        assertSame(cat.getClass(), Cat.class);
+    	Animal dog = MAPPER.convertValue(animalListDeserialized.get(1), Animal.class);
+    	assertTrue(dog instanceof Dog);
+        assertSame(dog.getClass(), Dog.class);
+    }
+
+    
+    /**
+     * Cars - serialization tests for no abstract method or type variable in base class
+     */
+    public void testExistingPropertySerializationCars() throws Exception
+    {
+        Map<String,Object> result = writeAndMap(MAPPER, camry);
+        assertEquals(3, result.size());
+        assertEquals(camry.name, result.get("name"));
+        assertEquals(camry.exteriorColor, result.get("exteriorColor"));
+        assertEquals(camry.getType(), result.get("type"));
+
+        result = writeAndMap(MAPPER, accord);
+        assertEquals(3, result.size());
+        assertEquals(accord.name, result.get("name"));
+        assertEquals(accord.speakerCount, result.get("speakerCount"));
+        assertEquals(accord.getType(), result.get("type"));
+        
+        String camrySerialized = MAPPER.writeValueAsString(camry);
+        assertEquals(camrySerialized, camryJson);
+        
+        String accordSerialized = MAPPER.writeValueAsString(accord);
+        assertEquals(accordSerialized, accordJson);
+        
+        String carWrapperSerialized = MAPPER.writeValueAsString(camryWrapper);
+        assertEquals(carWrapperSerialized, camryWrapperJson);
+
+        String carListSerialized = MAPPER.writeValueAsString(carList);
+        assertEquals(carListSerialized, carListJson);
+    }
+
+    /**
+     * Cars - deserialization tests for no abstract method or type variable in base class
+     */
+    public void testSimpleClassAsExistingPropertyDeserializationCars() throws Exception
+    {
+    	Car camryDeserialized = MAPPER.readValue(camryJson, Camry.class);
+    	assertTrue(camryDeserialized instanceof Camry);
+        assertSame(camryDeserialized.getClass(), Camry.class);
+    	assertEquals(camry.name, camryDeserialized.name);
+    	assertEquals(camry.exteriorColor, ((Camry) camryDeserialized).exteriorColor);
+    	assertEquals(camry.getType(), ((Camry) camryDeserialized).getType());
+
+    	CarWrapper camryWrapperDeserialized = MAPPER.readValue(camryWrapperJson, CarWrapper.class);
+    	Car camryExtracted = camryWrapperDeserialized.car;
+    	assertTrue(camryExtracted instanceof Camry);
+        assertSame(camryExtracted.getClass(), Camry.class);
+    	assertEquals(camry.name, camryExtracted.name);
+    	assertEquals(camry.exteriorColor, ((Camry) camryExtracted).exteriorColor);
+    	assertEquals(camry.getType(), ((Camry) camryExtracted).getType());
+    	
+    	@SuppressWarnings("unchecked")
+		List<Car> carListDeserialized = MAPPER.readValue(carListJson, List.class);
+    	assertNotNull(carListDeserialized);
+    	assertTrue(carListDeserialized.size() == 2);
+    	Car camry = MAPPER.convertValue(carListDeserialized.get(0), Car.class);
+    	assertTrue(camry instanceof Camry);
+        assertSame(camry.getClass(), Camry.class);
+    	Car accord = MAPPER.convertValue(carListDeserialized.get(1), Car.class);
+    	assertTrue(accord instanceof Accord);
+        assertSame(accord.getClass(), Accord.class);
+    }
+
+    
+}