AVRO-2648: Incorrect validation of numeric default values

Validation of numeric default values in Java is incorrect and results
in API inconsistencies. Consider the following examples:

Double values as int field default values:
public void testDoubleAsIntDefaultValue() {
  Schema.Field field = new Schema.Field("myField",
          Schema.create(Schema.Type.INT), "doc", 1.1);
  field.hasDefaultValue(); // true
  field.defaultValue(); // internal DoubleNode (1.1)
  field.defaultVal(); // null
  GenericData.get().getDefaultValue(field); // Integer (1)

  field = new Schema.Field("myField",
          Schema.create(Schema.Type.INT), "doc", 1.0);
  field.hasDefaultValue(); // true
  field.defaultValue(); // internal DoubleNode (1.0)
  field.defaultVal(); // null
  GenericData.get().getDefaultValue(field); // Integer (1)
}

Invalid long value as int field default value:
public void testInvalidLongAsIntDefault() {
  Schema.Field field = new Schema.Field("myField",
         Schema.create(Schema.Type.INT), "doc", Integer.MAX_VALUE + 1L);
  field.hasDefaultValue(); // true
  field.defaultValue(); // internal LongNode (2147483648)
  field.defaultVal(); // Long (2147483648)
  GenericData.get().getDefaultValue(field); // Integer (-2147483648)
}

This PR makes changes to invalidate incorrect default values for INT and
LONG schemas, including all floating point values, e.g. 1.0.
Additionally it contains changes to try and return the appropriate
Object type given the schema type.

This change is necessary for correctness and consitency but also
because users cannot disable default value validation and handle
these cases on their own since the underlying Field.defaultValue()
is no longer public. Users only have access to default values
mutated by Field.defaultVal() and GenericData.getDefaultValue().

Notes on JacksonUtils.toObject():
 - This method is used to convert the underlying JsonNode default value
  to an Object when Field.defaultVal() is called. This method is
  invoked regardless of whether default value validation is true or
  false.
 - For LongNode values we continue to return Long values for INT
   schemas in the case we cannot safely convert to an Integer.
   This behavior, while maintained, is inconsistent with that
   of FloatNode / DoubleNode where null is returned for INT
   and LONG schemas. Additional changes may be needed for
   further consistency.

Closes #739
(cherry picked from commit 8b5c11ade7f73eb74a0f017bd52b9b485d68d42f)
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Schema.java b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
index 8e152ae..6a0f7da 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Schema.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
@@ -1573,7 +1573,9 @@
     case FIXED:
       return defaultValue.isTextual();
     case INT:
+      return defaultValue.isIntegralNumber() && defaultValue.canConvertToInt();
     case LONG:
+      return defaultValue.isIntegralNumber() && defaultValue.canConvertToLong();
     case FLOAT:
     case DOUBLE:
       return defaultValue.isNumber();
diff --git a/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java b/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java
index 11f7fd1..1a82289 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/util/internal/JacksonUtils.java
@@ -116,9 +116,25 @@
         return jsonNode.asInt();
       } else if (schema.getType().equals(Schema.Type.LONG)) {
         return jsonNode.asLong();
+      } else if (schema.getType().equals(Schema.Type.FLOAT)) {
+        return (float) jsonNode.asDouble();
+      } else if (schema.getType().equals(Schema.Type.DOUBLE)) {
+        return jsonNode.asDouble();
       }
     } else if (jsonNode.isLong()) {
-      return jsonNode.asLong();
+      if (schema == null || schema.getType().equals(Schema.Type.LONG)) {
+        return jsonNode.asLong();
+      } else if (schema.getType().equals(Schema.Type.INT)) {
+        if (jsonNode.canConvertToInt()) {
+          return jsonNode.asInt();
+        } else {
+          return jsonNode.asLong();
+        }
+      } else if (schema.getType().equals(Schema.Type.FLOAT)) {
+        return (float) jsonNode.asDouble();
+      } else if (schema.getType().equals(Schema.Type.DOUBLE)) {
+        return jsonNode.asDouble();
+      }
     } else if (jsonNode.isDouble() || jsonNode.isFloat()) {
       if (schema == null || schema.getType().equals(Schema.Type.DOUBLE)) {
         return jsonNode.asDouble();
diff --git a/lang/java/avro/src/test/java/org/apache/avro/TestSchema.java b/lang/java/avro/src/test/java/org/apache/avro/TestSchema.java
index 4899b0e..b7e0dbe 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestSchema.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestSchema.java
@@ -31,6 +31,7 @@
 
 import org.apache.avro.Schema.Field;
 import org.apache.avro.Schema.Type;
+import org.apache.avro.generic.GenericData;
 import org.junit.Test;
 
 public class TestSchema {
@@ -215,4 +216,143 @@
     assertEquals(parent, parentWithoutInlinedChildReference);
   }
 
+  public void testIntDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", 1);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1, field.defaultVal());
+    assertEquals(1, GenericData.get().getDefaultValue(field));
+
+    field = new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", Integer.MIN_VALUE);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(Integer.MIN_VALUE, field.defaultVal());
+    assertEquals(Integer.MIN_VALUE, GenericData.get().getDefaultValue(field));
+
+    field = new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", Integer.MAX_VALUE);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(Integer.MAX_VALUE, field.defaultVal());
+    assertEquals(Integer.MAX_VALUE, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testValidLongAsIntDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", 1L);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1, field.defaultVal());
+    assertEquals(1, GenericData.get().getDefaultValue(field));
+
+    field = new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", Long.valueOf(Integer.MIN_VALUE));
+    assertTrue(field.hasDefaultValue());
+    assertEquals(Integer.MIN_VALUE, field.defaultVal());
+    assertEquals(Integer.MIN_VALUE, GenericData.get().getDefaultValue(field));
+
+    field = new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", Long.valueOf(Integer.MAX_VALUE));
+    assertTrue(field.hasDefaultValue());
+    assertEquals(Integer.MAX_VALUE, field.defaultVal());
+    assertEquals(Integer.MAX_VALUE, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test(expected = AvroTypeException.class)
+  public void testInvalidLongAsIntDefaultValue() {
+    new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", Integer.MAX_VALUE + 1L);
+  }
+
+  @Test(expected = AvroTypeException.class)
+  public void testDoubleAsIntDefaultValue() {
+    new Schema.Field("myField", Schema.create(Schema.Type.INT), "doc", 1.0);
+  }
+
+  @Test
+  public void testLongDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.LONG), "doc", 1L);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1L, field.defaultVal());
+    assertEquals(1L, GenericData.get().getDefaultValue(field));
+
+    field = new Schema.Field("myField", Schema.create(Schema.Type.LONG), "doc", Long.MIN_VALUE);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(Long.MIN_VALUE, field.defaultVal());
+    assertEquals(Long.MIN_VALUE, GenericData.get().getDefaultValue(field));
+
+    field = new Schema.Field("myField", Schema.create(Schema.Type.LONG), "doc", Long.MAX_VALUE);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(Long.MAX_VALUE, field.defaultVal());
+    assertEquals(Long.MAX_VALUE, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testIntAsLongDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.LONG), "doc", 1);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1L, field.defaultVal());
+    assertEquals(1L, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test(expected = AvroTypeException.class)
+  public void testDoubleAsLongDefaultValue() {
+    new Schema.Field("myField", Schema.create(Schema.Type.LONG), "doc", 1.0);
+  }
+
+  @Test
+  public void testDoubleDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.DOUBLE), "doc", 1.0);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0d, field.defaultVal());
+    assertEquals(1.0d, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testIntAsDoubleDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.DOUBLE), "doc", 1);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0d, field.defaultVal());
+    assertEquals(1.0d, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testLongAsDoubleDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.DOUBLE), "doc", 1L);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0d, field.defaultVal());
+    assertEquals(1.0d, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testFloatAsDoubleDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.DOUBLE), "doc", 1.0f);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0d, field.defaultVal());
+    assertEquals(1.0d, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testFloatDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.FLOAT), "doc", 1.0f);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0f, field.defaultVal());
+    assertEquals(1.0f, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testIntAsFloatDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.FLOAT), "doc", 1);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0f, field.defaultVal());
+    assertEquals(1.0f, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testLongAsFloatDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.FLOAT), "doc", 1L);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0f, field.defaultVal());
+    assertEquals(1.0f, GenericData.get().getDefaultValue(field));
+  }
+
+  @Test
+  public void testDoubleAsFloatDefaultValue() {
+    Schema.Field field = new Schema.Field("myField", Schema.create(Schema.Type.FLOAT), "doc", 1.0d);
+    assertTrue(field.hasDefaultValue());
+    assertEquals(1.0f, field.defaultVal());
+    assertEquals(1.0f, GenericData.get().getDefaultValue(field));
+  }
 }