Merge "Introduce cache schema for generic Struct class."
diff --git a/staticlibs/device/android/net/util/Struct.java b/staticlibs/device/android/net/util/Struct.java
index 92e05aa..03f5f22 100644
--- a/staticlibs/device/android/net/util/Struct.java
+++ b/staticlibs/device/android/net/util/Struct.java
@@ -29,6 +29,7 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Define a generic class that helps to parse the structured message.
@@ -142,6 +143,7 @@
             this.field = field;
         }
     }
+    private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap();
 
     private static void checkAnnotationType(final Type type, final Class fieldType) {
         switch (type) {
@@ -300,11 +302,15 @@
                     + Struct.class.getName());
         }
 
-        final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
+        final FieldInfo[] cachedAnnotationFields = sFieldCache.get(clazz);
+        if (cachedAnnotationFields != null) {
+            return cachedAnnotationFields;
+        }
 
         // Since array returned from Class#getDeclaredFields doesn't guarantee the actual order
         // of field appeared in the class, that is a problem when parsing raw data read from
         // ByteBuffer. Store the fields appeared by the order() defined in the Field annotation.
+        final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
         for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
             if (Modifier.isStatic(field.getModifiers())) continue;
 
@@ -324,6 +330,7 @@
             }
             annotationFields[annotation.order()] = new FieldInfo(annotation, field);
         }
+        sFieldCache.putIfAbsent(clazz, annotationFields);
         return annotationFields;
     }
 
diff --git a/staticlibs/tests/unit/src/android/net/util/StructTest.java b/staticlibs/tests/unit/src/android/net/util/StructTest.java
index 3451739..5ca278a 100644
--- a/staticlibs/tests/unit/src/android/net/util/StructTest.java
+++ b/staticlibs/tests/unit/src/android/net/util/StructTest.java
@@ -96,10 +96,7 @@
         }
     }
 
-    @Test
-    public void testClassWithExplicitConstructor() {
-        final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
-                HeaderMsgWithConstructor.class);
+    private void verifyHeaderParsing(final HeaderMsgWithConstructor msg) {
         assertEquals(10, msg.mFamily);
         assertEquals(0, msg.mLen);
         assertEquals(15715755, msg.mIfindex);
@@ -107,6 +104,13 @@
         assertEquals(0, msg.mIcmpCode);
     }
 
+    @Test
+    public void testClassWithExplicitConstructor() {
+        final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithConstructor.class);
+        verifyHeaderParsing(msg);
+    }
+
     static class HeaderMsgWithoutConstructor extends Struct {
         static int sType;
         static int sLength;
@@ -367,21 +371,22 @@
         }
     }
 
-    @Test
-    public void testPrefixArrayField() throws Exception {
-        final ByteBuffer buf = toByteBuffer(OPT_PREF64);
-        buf.order(ByteOrder.LITTLE_ENDIAN);
-
+    private void verifyPrefixByteArrayParsing(final PrefixMessage msg) throws Exception {
         // The original PREF64 option message has just 12 bytes for prefix byte array
         // (Highest 96 bits of the Prefix), copyOf pads the 128-bits IPv6 address with
         // prefix and 4-bytes zeros.
-        final PrefixMessage msg = Struct.parse(PrefixMessage.class, buf);
         final InetAddress addr = InetAddress.getByAddress(Arrays.copyOf(msg.mPrefix, 16));
         final IpPrefix prefix = new IpPrefix(addr, 96);
         assertEquals(10064, msg.mLifetime);
         assertTrue(prefix.equals(new IpPrefix("2001:db8:3:4:5:6::/96")));
     }
 
+    @Test
+    public void testPrefixArrayField() throws Exception {
+        final PrefixMessage msg = doParsingMessageTest(OPT_PREF64, PrefixMessage.class);
+        verifyPrefixByteArrayParsing(msg);
+    }
+
     static class HeaderMessageWithMutableField extends Struct {
         @Field(order = 0, type = Type.U8, padding = 1)
         final short mFamily;
@@ -481,6 +486,18 @@
         assertEquals(30806 /* 0x7856 */, msg.mInt2);
     }
 
+    @Test
+    public void testClassesParsedFromCache() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            final HeaderMsgWithConstructor msg1 =
+                    doParsingMessageTest(HDR_EMPTY, HeaderMsgWithConstructor.class);
+            verifyHeaderParsing(msg1);
+
+            final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class);
+            verifyPrefixByteArrayParsing(msg2);
+        }
+    }
+
     private ByteBuffer toByteBuffer(final String hexString) {
         return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
     }