From 670fc2a89d65d9855d50a62f87c1cc061b280215 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sat, 10 May 2014 11:02:07 +0200 Subject: Coordinates is an object with an array Ensure that the array is of a fixed length, add tests to check for that. --- src/data/Tweet.java | 10 +++++- src/data/ValidatingJsonDeserializer.java | 48 ++++++++++++++++++++++++--- src/database/QueryUtils.java | 8 ++++- test/data/ValidatingJsonDeserializerTest.java | 28 +++++++++++++++- 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/src/data/Tweet.java b/src/data/Tweet.java index 384a9e1..3caae6a 100644 --- a/src/data/Tweet.java +++ b/src/data/Tweet.java @@ -18,7 +18,8 @@ public class Tweet { @ValidatingJsonDeserializer.Validator public Place place; @ValidatingJsonDeserializer.Nullable - public String coordinates; + @ValidatingJsonDeserializer.Validator + public Coordinates coordinates; public String text; @ValidatingJsonDeserializer.Nullable @ValidatingJsonDeserializer.Validator @@ -45,6 +46,13 @@ public class Tweet { public String full_name; // "Danbury, CT" } + public static class Coordinates { + + //public String type; // always "Point"? + @ValidatingJsonDeserializer.ArrayValidator(minLen = 2, maxLen = 2) + public float[] coordinates; // e.g. [-73.49513755, 41.43286284] + } + public static class Entities { @ValidatingJsonDeserializer.Validator diff --git a/src/data/ValidatingJsonDeserializer.java b/src/data/ValidatingJsonDeserializer.java index 5dca3bd..431622b 100644 --- a/src/data/ValidatingJsonDeserializer.java +++ b/src/data/ValidatingJsonDeserializer.java @@ -31,7 +31,8 @@ public class ValidatingJsonDeserializer implements JsonDeserializer { return obj; } - void checkObject(String path, JsonElement je, Class type) { + void checkObject(String path, JsonElement je, Class type) + throws JsonParseException { JsonObject jsonObj = je.getAsJsonObject(); for (Field f : type.getDeclaredFields()) { JsonElement val = jsonObj.get(f.getName()); @@ -52,12 +53,33 @@ public class ValidatingJsonDeserializer implements JsonDeserializer { } } - private void tryValidateProperty(String path, JsonElement je, Field f) { + private void tryValidateProperty(String path, JsonElement je, Field f) + throws JsonParseException { + Class type = f.getType(); // assume that this annotation is only applied to objects Validator v = f.getAnnotation(Validator.class); + ArrayValidator av = f.getAnnotation(ArrayValidator.class); path += f.getName(); + if (av != null) { + if (!type.isArray()) { + throw new RuntimeException("Invalid " + av.getClass().getName() + + " + annotation for " + path); + } + if (!je.isJsonArray()) { + throw new JsonParseException("Expected array: " + path); + } + JsonArray ja = je.getAsJsonArray(); + int minLen = av.minLen(), maxLen = av.maxLen(); + if (minLen >= 0 && ja.size() < minLen) { + throw new JsonParseException("Array smaller than " + + minLen + ": " + path); + } + if (maxLen >= 0 && ja.size() > maxLen) { + throw new JsonParseException("Array larger than " + + maxLen + ": " + path); + } + } if (v != null) { - Class type = f.getType(); if (type.isArray()) { // the class expects an array, so the value must have one too. if (!je.isJsonArray()) { @@ -84,9 +106,25 @@ public class ValidatingJsonDeserializer implements JsonDeserializer { */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) - public @interface Validator { + public @interface ArrayValidator { - Class deserializer() default ValidatingJsonDeserializer.class; + /** + * @return Minimal length for array types (-1 if not checked). + */ + int minLen() default -1; + + /** + * @return Maximum length for array types (-1 if not checked). + */ + int maxLen() default -1; + } + + /** + * Marks a member as object that should be validated too. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface Validator { } /** diff --git a/src/database/QueryUtils.java b/src/database/QueryUtils.java index 228ccc8..66edd1c 100644 --- a/src/database/QueryUtils.java +++ b/src/database/QueryUtils.java @@ -94,7 +94,13 @@ public class QueryUtils { tweetStatement.setLong("favoritecount", tweet.favorite_count); tweetStatement.setLong("retweetcount", tweet.retweet_count); tweetStatement.setString("text", tweet.text); - tweetStatement.setString("coordinates", tweet.coordinates); + if (tweet.coordinates != null) { + float[] coords = tweet.coordinates.coordinates; + String coords_str = String.format("%f,%f", coords[0], coords[1]); + tweetStatement.setString("coordinates", coords_str); + } else { + tweetStatement.setString("coordinates", null); + } tweetStatement.setString("language", tweet.lang); if (tweet.retweeted_status != null) { tweetStatement.setLong("retweetid", tweet.retweeted_status.id); diff --git a/test/data/ValidatingJsonDeserializerTest.java b/test/data/ValidatingJsonDeserializerTest.java index 85f1661..b51631a 100644 --- a/test/data/ValidatingJsonDeserializerTest.java +++ b/test/data/ValidatingJsonDeserializerTest.java @@ -362,7 +362,7 @@ public class ValidatingJsonDeserializerTest { tweet.addProperty("created_at", "X"); tweet.addProperty("favorite_count", 4); tweet.add("place", JsonNull.INSTANCE); - tweet.addProperty("coordinates", "X"); + tweet.add("coordinates", JsonNull.INSTANCE); tweet.addProperty("text", "X"); tweet.add("retweeted_status", JsonNull.INSTANCE); // Tweet object JsonObject entities = new JsonObject(); @@ -468,6 +468,32 @@ public class ValidatingJsonDeserializerTest { checkTweetPass(tweet); } + @Test + public void testTweetCoordinates() { + JsonObject tweet = buildMinimalTweet(buildMinimalUser()); + addProperty(tweet, new JsonPrimitive("X"), "coordinates"); + checkTweetFail(tweet, "Expected object: coordinates"); + + JsonObject coords = new JsonObject(); + // overwrite coordinates with object + addProperty(tweet, coords, "coordinates"); + checkTweetFail(tweet, "Missing field: coordinates.coordinates"); + + // set coordinates.coordinates + JsonArray coordsFloat = new JsonArray(); + coords.add("coordinates", coordsFloat); + checkTweetFail(tweet, "Array smaller than 2: coordinates.coordinates"); + + coordsFloat.add(new JsonPrimitive(1.0f)); + checkTweetFail(tweet, "Array smaller than 2: coordinates.coordinates"); + + coordsFloat.add(new JsonPrimitive(1.0f)); + checkTweetPass(tweet); + + coordsFloat.add(new JsonPrimitive(1.0f)); + checkTweetFail(tweet, "Array larger than 2: coordinates.coordinates"); + } + @Test public void testTweetEntities() { checkImpairedTweet("entities"); -- cgit v1.2.1