Implemented [JACKSON-349], allow empty String as null/0 number

diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java b/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java
index 21f1723..b6b629f 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/ArrayDeserializers.java
@@ -293,7 +293,7 @@
             int ix = 0;
 
             while (jp.nextToken() != JsonToken.END_ARRAY) {
-                short value = _parseShort(jp, ctxt);
+                short value = _parseShortPrimitive(jp, ctxt);
                 if (ix >= chunk.length) {
                     chunk = builder.appendCompletedChunk(chunk, ix);
                     ix = 0;
@@ -322,7 +322,7 @@
 
             while (jp.nextToken() != JsonToken.END_ARRAY) {
                 // whether we should allow truncating conversions?
-                int value = _parseInt(jp, ctxt);
+                int value = _parseIntPrimitive(jp, ctxt);
                 if (ix >= chunk.length) {
                     chunk = builder.appendCompletedChunk(chunk, ix);
                     ix = 0;
@@ -350,7 +350,7 @@
             int ix = 0;
 
             while (jp.nextToken() != JsonToken.END_ARRAY) {
-                long value = _parseLong(jp, ctxt);
+                long value = _parseLongPrimitive(jp, ctxt);
                 if (ix >= chunk.length) {
                     chunk = builder.appendCompletedChunk(chunk, ix);
                     ix = 0;
@@ -379,7 +379,7 @@
 
             while (jp.nextToken() != JsonToken.END_ARRAY) {
                 // whether we should allow truncating conversions?
-                float value = _parseFloat(jp, ctxt);
+                float value = _parseFloatPrimitive(jp, ctxt);
                 if (ix >= chunk.length) {
                     chunk = builder.appendCompletedChunk(chunk, ix);
                     ix = 0;
@@ -407,7 +407,7 @@
             int ix = 0;
 
             while (jp.nextToken() != JsonToken.END_ARRAY) {
-                double value = _parseDouble(jp, ctxt);
+                double value = _parseDoublePrimitive(jp, ctxt);
                 if (ix >= chunk.length) {
                     chunk = builder.appendCompletedChunk(chunk, ix);
                     ix = 0;
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
index 101f455..5a71453 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/StdDeserializer.java
@@ -108,10 +108,17 @@
         throw ctxt.mappingException(_valueClass);
     }
 
-    protected final short _parseShort(JsonParser jp, DeserializationContext ctxt)
+    protected final Short _parseShort(JsonParser jp, DeserializationContext ctxt)
         throws IOException, JsonProcessingException
     {
-        int value = _parseInt(jp, ctxt);
+        JsonToken t = jp.getCurrentToken();
+        if (t == JsonToken.VALUE_NULL) {
+            return (short) 0;
+        }
+        if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
+            return jp.getShortValue();
+        }
+        int value = _parseIntPrimitive(jp, ctxt);
         // So far so good: but does it fit?
         if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
             throw ctxt.weirdStringException(_valueClass, "overflow, value can not be represented as 16-bit value");
@@ -119,7 +126,18 @@
         return (short) value;
     }
 
-    protected final int _parseInt(JsonParser jp, DeserializationContext ctxt)
+    protected final short _parseShortPrimitive(JsonParser jp, DeserializationContext ctxt)
+        throws IOException, JsonProcessingException
+    {
+        int value = _parseIntPrimitive(jp, ctxt);
+        // So far so good: but does it fit?
+        if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+            throw ctxt.weirdStringException(_valueClass, "overflow, value can not be represented as 16-bit value");
+        }
+        return (short) value;
+    }
+    
+    protected final int _parseIntPrimitive(JsonParser jp, DeserializationContext ctxt)
         throws IOException, JsonProcessingException
     {
         JsonToken t = jp.getCurrentToken();
@@ -134,16 +152,19 @@
              */
             String text = jp.getText().trim();
             try {
-                if (text.length() > 9) {
+                int len = text.length();
+                if (len > 9) {
                     long l = Long.parseLong(text);
                     if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
                         throw ctxt.weirdStringException(_valueClass,
                             "Overflow: numeric value ("+text+") out of range of int ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")");
                     }
                     return (int) l;
-                } else {
-                    return Integer.parseInt(text);
                 }
+                if (len == 0) {
+                    return 0;
+                }
+                return Integer.parseInt(text);
             } catch (IllegalArgumentException iae) {
                 throw ctxt.weirdStringException(_valueClass, "not a valid int value");
             }
@@ -155,11 +176,45 @@
         throw ctxt.mappingException(_valueClass);
     }
 
-    protected final long _parseLong(JsonParser jp, DeserializationContext ctxt)
+    protected final Integer _parseInteger(JsonParser jp, DeserializationContext ctxt)
         throws IOException, JsonProcessingException
     {
         JsonToken t = jp.getCurrentToken();
+        if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
+            return Integer.valueOf(jp.getIntValue());
+        }
+        if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
+            String text = jp.getText().trim();
+            try {
+                int len = text.length();
+                if (len > 9) {
+                    long l = Long.parseLong(text);
+                    if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
+                        throw ctxt.weirdStringException(_valueClass,
+                            "Overflow: numeric value ("+text+") out of range of Integer ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")");
+                    }
+                    return Integer.valueOf((int) l);
+                }
+                if (len == 0) {
+                    return null;
+                }
+                return Integer.parseInt(text);
+            } catch (IllegalArgumentException iae) {
+                throw ctxt.weirdStringException(_valueClass, "not a valid Integer value");
+            }
+        }
+        if (t == JsonToken.VALUE_NULL) {
+            return null;
+        }
+        // Otherwise, no can do:
+        throw ctxt.mappingException(_valueClass);
+    }
 
+    protected final Long _parseLong(JsonParser jp, DeserializationContext ctxt)
+        throws IOException, JsonProcessingException
+    {
+        JsonToken t = jp.getCurrentToken();
+    
         // it should be ok to coerce (although may fail, too)
         if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
             return jp.getLongValue();
@@ -168,16 +223,45 @@
         if (t == JsonToken.VALUE_STRING) {
             // !!! 05-Jan-2009, tatu: Should we try to limit value space, JDK is too lenient?
             String text = jp.getText().trim();
+            if (text.length() == 0) {
+                return null;
+            }
             try {
                 return Long.parseLong(text);
             } catch (IllegalArgumentException iae) { }
-            throw ctxt.weirdStringException(_valueClass, "not a valid long value");
+            throw ctxt.weirdStringException(_valueClass, "not a valid Long value");
+        }
+        if (t == JsonToken.VALUE_NULL) {
+            return null;
         }
         // Otherwise, no can do:
         throw ctxt.mappingException(_valueClass);
     }
 
-    protected final float _parseFloat(JsonParser jp, DeserializationContext ctxt)
+    protected final long _parseLongPrimitive(JsonParser jp, DeserializationContext ctxt)
+        throws IOException, JsonProcessingException
+    {
+        JsonToken t = jp.getCurrentToken();
+        if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
+            return jp.getLongValue();
+        }
+        if (t == JsonToken.VALUE_STRING) {
+            String text = jp.getText().trim();
+            if (text.length() == 0) {
+                return 0L;
+            }
+            try {
+                return Long.parseLong(text);
+            } catch (IllegalArgumentException iae) { }
+            throw ctxt.weirdStringException(_valueClass, "not a valid long value");
+        }
+        if (t == JsonToken.VALUE_NULL) {
+            return 0L;
+        }
+        throw ctxt.mappingException(_valueClass);
+    }
+    
+    protected final Float _parseFloat(JsonParser jp, DeserializationContext ctxt)
         throws IOException, JsonProcessingException
     {
         // We accept couple of different types; obvious ones first:
@@ -188,31 +272,73 @@
         }
         // And finally, let's allow Strings to be converted too
         if (t == JsonToken.VALUE_STRING) {
-            // !!! 05-Jan-2009, tatu: Should we try to limit value space, JDK is too lenient?
             String text = jp.getText().trim();
-            if (text.length() > 1) {
-                switch (text.charAt(0)) {
-                case 'I':
-                    if ("Infinity".equals(text) || "INF".equals(text)) {
-                        return Float.POSITIVE_INFINITY;
-                    }
-                    break;
-                case 'N':
-                    if ("NaN".equals(text)) {
-                        return Float.NaN;
-                    }
-                    break;
-                case '-':
-                    if ("-Infinity".equals(text) || "-INF".equals(text)) {
-                        return Float.NEGATIVE_INFINITY;
-                    }
-                    break;
+            if (text.length() == 0) {
+                return null;
+            }
+            switch (text.charAt(0)) {
+            case 'I':
+                if ("Infinity".equals(text) || "INF".equals(text)) {
+                    return Float.POSITIVE_INFINITY;
                 }
+                break;
+            case 'N':
+                if ("NaN".equals(text)) {
+                    return Float.NaN;
+                }
+                break;
+            case '-':
+                if ("-Infinity".equals(text) || "-INF".equals(text)) {
+                    return Float.NEGATIVE_INFINITY;
+                }
+                break;
             }
             try {
                 return Float.parseFloat(text);
             } catch (IllegalArgumentException iae) { }
-            throw ctxt.weirdStringException(_valueClass, "not a valid double value");
+            throw ctxt.weirdStringException(_valueClass, "not a valid Float value");
+        }
+        if (t == JsonToken.VALUE_NULL) {
+            return null;
+        }
+        // Otherwise, no can do:
+        throw ctxt.mappingException(_valueClass);
+    }
+
+    protected final float _parseFloatPrimitive(JsonParser jp, DeserializationContext ctxt)
+        throws IOException, JsonProcessingException
+    {
+        JsonToken t = jp.getCurrentToken();
+        
+        if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
+            return jp.getFloatValue();
+        }
+        if (t == JsonToken.VALUE_STRING) {
+            String text = jp.getText().trim();
+            if (text.length() == 0) {
+                return 0.0f;
+            }
+            switch (text.charAt(0)) {
+            case 'I':
+                if ("Infinity".equals(text) || "INF".equals(text)) {
+                    return Float.POSITIVE_INFINITY;
+                }
+                break;
+            case 'N':
+                if ("NaN".equals(text)) {
+                    return Float.NaN;
+                }
+                break;
+            case '-':
+                if ("-Infinity".equals(text) || "-INF".equals(text)) {
+                    return Float.NEGATIVE_INFINITY;
+                }
+                break;
+            }
+            try {
+                return Float.parseFloat(text);
+            } catch (IllegalArgumentException iae) { }
+            throw ctxt.weirdStringException(_valueClass, "not a valid float value");
         }
         if (t == JsonToken.VALUE_NULL) {
             return 0.0f;
@@ -221,7 +347,49 @@
         throw ctxt.mappingException(_valueClass);
     }
 
-    protected final double _parseDouble(JsonParser jp, DeserializationContext ctxt)
+    protected final Double _parseDouble(JsonParser jp, DeserializationContext ctxt)
+        throws IOException, JsonProcessingException
+    {
+        JsonToken t = jp.getCurrentToken();
+        
+        if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) { // coercing should work too
+            return jp.getDoubleValue();
+        }
+        if (t == JsonToken.VALUE_STRING) {
+            String text = jp.getText().trim();
+            if (text.length() == 0) {
+                return null;
+            }
+            switch (text.charAt(0)) {
+            case 'I':
+                if ("Infinity".equals(text) || "INF".equals(text)) {
+                    return Double.POSITIVE_INFINITY;
+                }
+                break;
+            case 'N':
+                if ("NaN".equals(text)) {
+                    return Double.NaN;
+                }
+                break;
+            case '-':
+                if ("-Infinity".equals(text) || "-INF".equals(text)) {
+                    return Double.NEGATIVE_INFINITY;
+                }
+                break;
+            }
+            try {
+                return Double.parseDouble(text);
+            } catch (IllegalArgumentException iae) { }
+            throw ctxt.weirdStringException(_valueClass, "not a valid Double value");
+        }
+        if (t == JsonToken.VALUE_NULL) {
+            return null;
+        }
+            // Otherwise, no can do:
+        throw ctxt.mappingException(_valueClass);
+    }
+
+    protected final double _parseDoublePrimitive(JsonParser jp, DeserializationContext ctxt)
         throws IOException, JsonProcessingException
     {
         // We accept couple of different types; obvious ones first:
@@ -233,24 +401,25 @@
         // And finally, let's allow Strings to be converted too
         if (t == JsonToken.VALUE_STRING) {
             String text = jp.getText().trim();
-            if (text.length() > 1) {
-                switch (text.charAt(0)) {
-                case 'I':
-                    if ("Infinity".equals(text) || "INF".equals(text)) {
-                        return Double.POSITIVE_INFINITY;
-                    }
-                    break;
-                case 'N':
-                    if ("NaN".equals(text)) {
-                        return Double.NaN;
-                    }
-                    break;
-                case '-':
-                    if ("-Infinity".equals(text) || "-INF".equals(text)) {
-                        return Double.NEGATIVE_INFINITY;
-                    }
-                    break;
+            if (text.length() == 0) {
+                return 0.0;
+            }
+            switch (text.charAt(0)) {
+            case 'I':
+                if ("Infinity".equals(text) || "INF".equals(text)) {
+                    return Double.POSITIVE_INFINITY;
                 }
+                break;
+            case 'N':
+                if ("NaN".equals(text)) {
+                    return Double.NaN;
+                }
+                break;
+            case '-':
+                if ("-Infinity".equals(text) || "-INF".equals(text)) {
+                    return Double.NEGATIVE_INFINITY;
+                }
+                break;
             }
             try {
                 return Double.parseDouble(text);
@@ -264,6 +433,7 @@
         throw ctxt.mappingException(_valueClass);
     }
 
+    
     protected java.util.Date _parseDate(JsonParser jp, DeserializationContext ctxt)
         throws IOException, JsonProcessingException
     {
@@ -514,7 +684,7 @@
         public Byte deserialize(JsonParser jp, DeserializationContext ctxt)
             throws IOException, JsonProcessingException
         {
-            int value = _parseInt(jp, ctxt);
+            int value = _parseIntPrimitive(jp, ctxt);
             // So far so good: but does it fit?
             if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
                 throw ctxt.weirdStringException(_valueClass, "overflow, value can not be represented as 8-bit value");
@@ -536,7 +706,7 @@
         public Short deserialize(JsonParser jp, DeserializationContext ctxt)
             throws IOException, JsonProcessingException
         {
-            return Short.valueOf(_parseShort(jp, ctxt));
+            return _parseShort(jp, ctxt);
         }
     }
 
@@ -585,7 +755,7 @@
         public Integer deserialize(JsonParser jp, DeserializationContext ctxt)
             throws IOException, JsonProcessingException
         {
-            return _parseInt(jp, ctxt);
+            return _parseInteger(jp, ctxt);
         }
 
         // 1.6: since we can never have type info ("natural type"; String, Boolean, Integer, Double):
@@ -595,7 +765,7 @@
                 TypeDeserializer typeDeserializer)
             throws IOException, JsonProcessingException
         {
-            return _parseInt(jp, ctxt);
+            return _parseInteger(jp, ctxt);
         }
     }
 
@@ -612,7 +782,7 @@
         public Long deserialize(JsonParser jp, DeserializationContext ctxt)
             throws IOException, JsonProcessingException
         {
-            return Long.valueOf(_parseLong(jp, ctxt));
+            return _parseLong(jp, ctxt);
         }
     }
 
@@ -632,7 +802,7 @@
             /* 22-Jan-2009, tatu: Bounds/range checks would be tricky
              *   here, so let's not bother even trying...
              */
-            return Float.valueOf((float) _parseDouble(jp, ctxt));
+            return _parseFloat(jp, ctxt);
         }
     }
 
@@ -831,13 +1001,15 @@
             throws IOException, JsonProcessingException
         {
             JsonToken t = jp.getCurrentToken();
-            if (t == JsonToken.VALUE_NUMBER_INT
-                || t == JsonToken.VALUE_NUMBER_FLOAT) {
+            if (t == JsonToken.VALUE_NUMBER_INT || t == JsonToken.VALUE_NUMBER_FLOAT) {
                 return jp.getDecimalValue();
             }
             // String is ok too, can easily convert
             if (t == JsonToken.VALUE_STRING) { // let's do implicit re-parse
                 String text = jp.getText().trim();
+                if (text.length() == 0) {
+                    return null;
+                }
                 try {
                     return new BigDecimal(text);
                 } catch (IllegalArgumentException iae) {
@@ -882,6 +1054,9 @@
                 throw ctxt.mappingException(_valueClass);
             }
             text = jp.getText().trim();
+            if (text.length() == 0) {
+                return null;
+            }
             try {
                 return new BigInteger(text);
             } catch (IllegalArgumentException iae) {
diff --git a/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java b/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java
index 188ebe7..875e498 100644
--- a/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java
+++ b/src/mapper/java/org/codehaus/jackson/map/deser/StdKeyDeserializer.java
@@ -13,7 +13,7 @@
 public abstract class StdKeyDeserializer
     extends KeyDeserializer
 {
-    final Class<?> _keyClass;
+    final protected Class<?> _keyClass;
 
     protected StdKeyDeserializer(Class<?> cls) { _keyClass = cls; }
 
@@ -40,9 +40,9 @@
     protected abstract Object _parse(String key, DeserializationContext ctxt) throws Exception;
 
     /*
-    ////////////////////////////////////////////////////////////////////////
-    // Helper methods for sub-classes
-    ////////////////////////////////////////////////////////////////////////
+    /**********************************************************
+    /* Helper methods for sub-classes
+    /**********************************************************
      */
 
     protected int _parseInt(String key) throws IllegalArgumentException
@@ -61,9 +61,9 @@
     }
 
     /*
-    ////////////////////////////////////////////////////////////////////////
-    // Key deserializer implementations; wrappers
-    ////////////////////////////////////////////////////////////////////////
+    /**********************************************************
+    /* Key deserializer implementations; wrappers
+    /**********************************************************
      */
 
     final static class BoolKD extends StdKeyDeserializer
@@ -147,7 +147,7 @@
         LongKD() { super(Long.class); }
 
         @Override
-		public Long _parse(String key, DeserializationContext ctxt) throws JsonMappingException
+        public Long _parse(String key, DeserializationContext ctxt) throws JsonMappingException
         {
             return _parseLong(key);
         }
@@ -158,7 +158,7 @@
         DoubleKD() { super(Double.class); }
 
         @Override
-		public Double _parse(String key, DeserializationContext ctxt) throws JsonMappingException
+        public Double _parse(String key, DeserializationContext ctxt) throws JsonMappingException
         {
             return _parseDouble(key);
         }
@@ -169,7 +169,7 @@
         FloatKD() { super(Float.class); }
 
         @Override
-		public Float _parse(String key, DeserializationContext ctxt) throws JsonMappingException
+        public Float _parse(String key, DeserializationContext ctxt) throws JsonMappingException
         {
             /* 22-Jan-2009, tatu: Bounds/range checks would be tricky
              *   here, so let's not bother even trying...
@@ -179,9 +179,9 @@
     }
 
     /*
-    ////////////////////////////////////////////////////////////////////////
-    // Key deserializer implementations; other
-    ////////////////////////////////////////////////////////////////////////
+    /**********************************************************
+    /* Key deserializer implementations; other
+    /**********************************************************
      */
 
     final static class EnumKD extends StdKeyDeserializer
diff --git a/src/test/org/codehaus/jackson/map/deser/TestNumbers.java b/src/test/org/codehaus/jackson/map/deser/TestNumbers.java
index a173254..c66d401 100644
--- a/src/test/org/codehaus/jackson/map/deser/TestNumbers.java
+++ b/src/test/org/codehaus/jackson/map/deser/TestNumbers.java
@@ -1,5 +1,8 @@
 package org.codehaus.jackson.map.deser;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
 import org.codehaus.jackson.map.*;
 
 /**
@@ -24,4 +27,16 @@
         result = m.readValue(" \""+Double.NEGATIVE_INFINITY+"\"", Double.class);
         assertEquals(Double.valueOf(Double.NEGATIVE_INFINITY), result);
     }
+
+    // [JACKSON-349]
+    public void testEmptyAsNumber() throws Exception
+    {
+        ObjectMapper m = new ObjectMapper();
+        assertNull(m.readValue(quote(""), Integer.class));
+        assertNull(m.readValue(quote(""), Long.class));
+        assertNull(m.readValue(quote(""), Float.class));
+        assertNull(m.readValue(quote(""), Double.class));
+        assertNull(m.readValue(quote(""), BigInteger.class));
+        assertNull(m.readValue(quote(""), BigDecimal.class));
+    }
 }