You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by et...@apache.org on 2009/02/12 06:19:38 UTC

svn commit: r743623 - in /incubator/shindig/trunk/java/common/src: main/java/org/apache/shindig/common/ test/java/org/apache/shindig/common/

Author: etnu
Date: Thu Feb 12 05:19:37 2009
New Revision: 743623

URL: http://svn.apache.org/viewvc?rev=743623&view=rev
Log:
Added pojo support to JsonSerializer. We can't quite replace all usage of ad-hoc json serialization because REST and JSON-RPC disagree about some property names in the social api code.


Added:
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonProperty.java
    incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonAssert.java
Modified:
    incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonSerializer.java
    incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonSerializerTest.java

Added: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonProperty.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonProperty.java?rev=743623&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonProperty.java (added)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonProperty.java Thu Feb 12 05:19:37 2009
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.shindig.common;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for specifying a property name other than the default when using JsonSerializer.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface JsonProperty {
+  String value();
+}

Modified: incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonSerializer.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonSerializer.java?rev=743623&r1=743622&r2=743623&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonSerializer.java (original)
+++ incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/common/JsonSerializer.java Thu Feb 12 05:19:37 2009
@@ -18,13 +18,21 @@
  */
 package org.apache.shindig.common;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+
+import org.joda.time.DateTime;
 import org.json.JSONArray;
 import org.json.JSONObject;
 
 import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.Collection;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Serializes a JSONObject.
@@ -44,8 +52,23 @@
     '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
   };
 
+  private static final Set<String> EXCLUDE_METHODS
+      = ImmutableSet.of("getClass", "getDeclaringClass");
+
+  private static final Map<Class<?>, Map<String, Method>> getters = Maps.newConcurrentHashMap();
+
   private JsonSerializer() {}
 
+  public static String serialize(Object object) {
+    StringBuilder buf = new StringBuilder(1024);
+    try {
+      append(buf, object);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return buf.toString();
+  }
+
   /**
    * Serialize a JSONObject. Does not guard against cyclical references.
    */
@@ -125,26 +148,66 @@
   public static void append(Appendable buf, Object value) throws IOException {
     if (value == null) {
       buf.append("null");
-    } else if (value instanceof JSONObject) {
-      appendJsonObject(buf, (JSONObject)value);
-    } else if (value instanceof String) {
-      appendString(buf, (String)value);
-    } else if (value instanceof Number || value instanceof Boolean) {
+    } else if (value instanceof Number ||
+               value instanceof Boolean) {
+      // Primitives
       buf.append(value.toString());
+    } else if (value instanceof CharSequence ||
+               value instanceof DateTime ||
+               value instanceof Date ||
+               value.getClass().isEnum()) {
+      // String-like Primitives
+      appendString(buf, value.toString());
+    } else if (value instanceof JSONObject) {
+      appendJsonObject(buf, (JSONObject) value);
     } else if (value instanceof JSONArray) {
       buf.append(value.toString());
     } else if (value instanceof Map) {
-      appendMap(buf, (Map<String, Object>)value);
+      appendMap(buf, (Map<String, Object>) value);
     } else if (value instanceof Collection) {
-      appendCollection(buf, (Collection<Object>)value);
+      appendCollection(buf, (Collection<Object>) value);
     } else if (value.getClass().isArray()) {
-      appendArray(buf, (Object[])value);
+      appendArray(buf, (Object[]) value);
     } else {
-      appendString(buf, value.toString());
+      // Try getter conversion
+      appendPojo(buf, value);
     }
   }
 
   /**
+   * Appends a java object using getters
+   *
+   * @throws IOException If {@link Appendable#append(char)} throws an exception.
+   */
+  public static void appendPojo(Appendable buf, Object pojo) throws IOException {
+    Map<String, Method> methods = getGetters(pojo);
+    buf.append('{');
+    boolean firstDone = false;
+    for (Map.Entry<String, Method> entry : methods.entrySet()) {
+      if (firstDone) {
+        buf.append(',');
+      } else {
+        firstDone = true;
+      }
+      appendString(buf, entry.getKey());
+      buf.append(':');
+      try {
+        append(buf, entry.getValue().invoke(pojo));
+      } catch (IllegalArgumentException e) {
+        // Shouldn't be possible.
+        throw new RuntimeException(e);
+      } catch (IllegalAccessException e) {
+        // Bad class.
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        // Bad class.
+        throw new RuntimeException(e);
+      }
+    }
+    buf.append('}');
+  }
+
+  /**
    * Appends an array to the buffer.
    *
    * @throws IOException If {@link Appendable#append(char)} throws an exception.
@@ -337,4 +400,38 @@
     }
     buf.append('"');
   }
+
+  private static Map<String, Method> getGetters(Object pojo) {
+    Class<?> clazz = pojo.getClass();
+
+    Map<String, Method> methods = getters.get(clazz);
+    if (methods != null) {
+      return methods;
+    }
+    // Ensure consistent method ordering by using a linked hash map.
+    methods = Maps.newHashMap();
+
+    for (Method method : clazz.getMethods()) {
+      String name = getPropertyName(method);
+      if (name != null) {
+        methods.put(name, method);
+      }
+    }
+
+    getters.put(clazz, methods);
+    return methods;
+  }
+
+  private static String getPropertyName(Method method) {
+    JsonProperty property = method.getAnnotation(JsonProperty.class);
+    if (property == null) {
+      String name = method.getName();
+      if (name.startsWith("get") && (!EXCLUDE_METHODS.contains(name))) {
+        return name.substring(3, 4).toLowerCase() + name.substring(4);
+      }
+      return null;
+    } else {
+      return property.value();
+    }
+  }
 }

Added: incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonAssert.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonAssert.java?rev=743623&view=auto
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonAssert.java (added)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonAssert.java Thu Feb 12 05:19:37 2009
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.common;
+
+import static org.junit.Assert.assertEquals;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public final class JsonAssert {
+  private JsonAssert() {}
+
+  public static void assertJsonArrayEquals(JSONArray left, JSONArray right) throws Exception {
+    if (left.length() != right.length()) {
+      assertEquals("Arrays are not of equal length", left.toString(), right.toString());
+    }
+
+    for (int i = 0; i < left.length(); ++i) {
+      Object leftValue = left.opt(i);
+      Object rightValue = right.opt(i);
+
+      assertEquals(left.toString() + " != " + right.toString(),
+                   leftValue.getClass(), rightValue.getClass());
+
+      if (leftValue instanceof JSONObject) {
+        assertJsonObjectEquals((JSONObject) leftValue, (JSONObject) rightValue);
+      } else if (leftValue instanceof JSONArray) {
+        assertJsonArrayEquals((JSONArray) leftValue, (JSONArray) rightValue);
+      } else {
+        assertEquals(leftValue, rightValue);
+      }
+    }
+  }
+
+  public static void assertJsonObjectEquals(JSONObject left, JSONObject right) throws Exception {
+    if (left.length() != right.length()) {
+      assertEquals("Objects are not of equal size", left.toString(2), right.toString(2));
+    }
+
+    for (String name : JSONObject.getNames(left)) {
+      Object leftValue = left.opt(name);
+      Object rightValue = right.opt(name);
+
+      assertEquals(left.toString() + " != " + right.toString(),
+                   leftValue.getClass(), rightValue.getClass());
+
+      if (leftValue instanceof JSONObject) {
+        assertJsonObjectEquals((JSONObject) leftValue, (JSONObject) rightValue);
+      } else if (leftValue instanceof JSONArray) {
+        assertJsonArrayEquals((JSONArray) leftValue, (JSONArray) rightValue);
+      } else {
+        assertEquals(leftValue, rightValue);
+      }
+    }
+  }
+
+  public static void assertJsonEquals(String left, String right) throws Exception {
+    switch (left.charAt(0)) {
+      case '{':
+        assertJsonObjectEquals(new JSONObject(left), new JSONObject(right));
+        break;
+      case '[':
+        assertJsonArrayEquals(new JSONArray(left), new JSONArray(right));
+        break;
+      default:
+        assertEquals(left, right);
+        break;
+    }
+  }
+}

Modified: incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonSerializerTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonSerializerTest.java?rev=743623&r1=743622&r2=743623&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonSerializerTest.java (original)
+++ incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/common/JsonSerializerTest.java Thu Feb 12 05:19:37 2009
@@ -18,17 +18,18 @@
  */
 package org.apache.shindig.common;
 
+import static org.apache.shindig.common.JsonAssert.assertJsonEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
-import com.google.common.collect.Maps;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
 
 import org.apache.commons.lang.StringUtils;
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.junit.Test;
 
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Collection;
@@ -44,58 +45,100 @@
 
   @Test
   public void serializeSimpleJsonObject() throws Exception {
-    JSONObject json = new JSONObject("{\"foo\":\"bar\"}");
-    assertTrue("Did not produce results matching reference implementation.",
-               jsonEquals(json.toString(), JsonSerializer.serialize(json)));
+    String json = "{foo:'bar'}";
+    assertJsonEquals(json, JsonSerializer.serialize(new JSONObject(json)));
   }
 
   @Test
   public void serializeSimpleMap() throws Exception {
     Map<String, String> map = ImmutableMap.of("hello", "world", "foo", "bar");
-    assertTrue("Did not produce results matching reference implementation.",
-        jsonEquals(new JSONObject(map).toString(), JsonSerializer.serialize(map)));
+    assertJsonEquals("{hello:'world',foo:'bar'}", JsonSerializer.serialize(map));
   }
 
   @Test
   public void serializeSimpleCollection() throws Exception {
     Collection<String> collection = Arrays.asList("foo", "bar", "baz");
-    assertEquals("[\"foo\",\"bar\",\"baz\"]", JsonSerializer.serialize(collection));
+    assertJsonEquals("['foo','bar','baz']", JsonSerializer.serialize(collection));
   }
 
   @Test
   public void serializeArray() throws Exception {
     String[] array = new String[] {"foo", "bar", "baz"};
-    assertEquals("[\"foo\",\"bar\",\"baz\"]", JsonSerializer.serialize(array));
+    assertJsonEquals("['foo','bar','baz']", JsonSerializer.serialize(array));
   }
 
   @Test
   public void serializeJsonArray() throws Exception {
     JSONArray array = new JSONArray(new String[] {"foo", "bar", "baz"});
-    assertEquals("[\"foo\",\"bar\",\"baz\"]", JsonSerializer.serialize(array));
+    assertJsonEquals("['foo','bar','baz']", JsonSerializer.serialize(array));
+  }
+
+  @Test
+  public void serializePrimitives() throws Exception {
+    assertEquals("\"hello\"", JsonSerializer.serialize("hello"));
+    assertEquals("100", JsonSerializer.serialize(100));
+    assertEquals("125.0", JsonSerializer.serialize(125.0f));
+    assertEquals("126.0", JsonSerializer.serialize(126.0));
+    assertEquals("1", JsonSerializer.serialize(1L));
+    assertEquals("\"RUNTIME\"", JsonSerializer.serialize(RetentionPolicy.RUNTIME));
+    assertEquals("\"string buf\"",
+        JsonSerializer.serialize(new StringBuilder().append("string").append(' ').append("buf")));
+  }
+
+  public static class JsonPojo {
+    public String getString() {
+      return "string-value";
+    }
+
+    @SuppressWarnings("unused")
+    private String getPrivateString() {
+      throw new UnsupportedOperationException();
+    }
+
+    public int getInteger() {
+      return 100;
+    }
+
+    @JsonProperty("simple!")
+    public int getSimpleName() {
+      return 3;
+    }
+
+  }
+
+  @Test
+  public void serializePojo() throws Exception {
+    JsonPojo pojo = new JsonPojo();
+
+    assertJsonEquals("{string:'string-value',integer:100,'simple!':3}",
+        JsonSerializer.serialize(pojo));
   }
 
   @Test
   public void serializeMixedObjects() throws Exception {
     Map<String, ?> map = ImmutableMap.of(
-        "integer", Integer.valueOf(100),
-        "double", Double.valueOf(233333333333.7d),
-        "boolean", Boolean.TRUE,
+        "int", Integer.valueOf(3),
+        "double", Double.valueOf(2.7d),
+        "bool", Boolean.TRUE,
         "map", ImmutableMap.of("hello", "world", "foo", "bar"),
         "string", "hello!");
-    assertTrue("Did not produce results matching reference implementation.",
-        jsonEquals(new JSONObject(map).toString(), JsonSerializer.serialize(map)));
+    assertJsonEquals(
+        "{int:3,double:2.7,bool:true,map:{hello:'world',foo:'bar'},string:'hello!'}",
+        JsonSerializer.serialize(map));
   }
 
   @Test
   public void serializeMixedArray() throws Exception {
     Collection<Object> data = Arrays.asList(
-        "integer", Integer.valueOf(100),
-        "double", Double.valueOf(233333333333.7d),
-        "boolean", Boolean.TRUE,
+        Integer.valueOf(3),
+        Double.valueOf(2.7d),
+        Boolean.TRUE,
         Arrays.asList("one", "two", "three"),
         new JSONArray(new String[] {"foo", "bar"}),
-        "string", "hello!");
-    assertEquals(new JSONArray(data).toString(), JsonSerializer.serialize(data));
+        "hello!");
+    assertJsonEquals(
+        "[3,2.7,true,['one','two','three'],['foo','bar'],'hello!']",
+        JsonSerializer.serialize(data));
   }
 
   @Test
@@ -199,37 +242,6 @@
     return data;
   }
 
-  private static boolean jsonEquals(JSONObject left, JSONObject right) {
-    if (left.length() != right.length()) {
-      return false;
-    }
-    for (String name : JSONObject.getNames(left)) {
-      Object leftValue = left.opt(name);
-      Object rightValue = right.opt(name);
-      if (leftValue instanceof JSONObject) {
-        if (!jsonEquals((JSONObject)leftValue, (JSONObject)rightValue)) {
-          return false;
-        }
-      } else if (leftValue instanceof JSONArray) {
-        JSONArray leftArray = (JSONArray)leftValue;
-        JSONArray rightArray = (JSONArray)rightValue;
-        for (int i = 0; i < leftArray.length(); ++i) {
-          if (!(leftArray.opt(i).equals(rightArray.opt(i)))) {
-            return false;
-          }
-        }
-      } else if (!leftValue.equals(rightValue)) {
-        System.out.println("Not a match: " + leftValue + " != " + rightValue);
-        return false;
-      }
-    }
-    return true;
-  }
-
-  private static boolean jsonEquals(String reference, String comparison) throws Exception {
-    return jsonEquals(new JSONObject(reference), new JSONObject(comparison));
-  }
-
   @SuppressWarnings("unchecked")
   public static void main(String[] args) throws Exception {
     int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 1000;
@@ -240,13 +252,8 @@
         Map<String, Object> data = (Map<String, Object>)method.invoke(null);
         System.out.println("Running: " + method.getName());
 
-        String jsonOrg = runJsonOrgTest(data, iterations);
-        String serializer = runSerializerTest(data, iterations);
-        // String netSfJson = runNetSfJsonTest(data, iterations);
-
-        if (!jsonEquals(jsonOrg, serializer)) {
-          System.out.println("Serializer did not produce results matching the reference impl.");
-        }
+        runJsonOrgTest(data, iterations);
+        runSerializerTest(data, iterations);
 
         // if (!jsonEquals(jsonOrg, netSfJson)) {
         //   System.out.println("net.sf.json did not produce results matching the reference impl.");