Add new Cache interface and LRU Cache implementation to cache field annotations per "Class" (rather than per instance). This results in a significant speed-up (approx. 3X) when serializing/deserializing the same classes over and over again.

This commit is contained in:
Joel Leitch 2010-01-10 00:32:21 +00:00
parent e6496fddd6
commit 9816426bba
15 changed files with 241 additions and 30 deletions

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed 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 com.google.gson;
/**
* Defines generic cache interface.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
interface Cache<K, V> {
/**
* Adds the new value object into the cache for the given key. If the key already
* exists, then this method will override the value for the key.
*
* @param key the key identifier for the {@code value} object
* @param value the value object to store in the cache
*/
void addElement(K key, V value);
/**
* Retrieve the cached value for the given {@code key}.
*
* @param key the key identifying the value
* @return the cached value for the given {@code key}
*/
V getElement(K key);
/**
* Removes the value from the cache for the given key.
*
* @param key the key identifying the value to remove
* @return the value for the given {@code key}
*/
V removeElement(K key);
/**
* Removes everything from this cache.
*/
void clear();
/**
* @return the number of objects in this cache
*/
int size();
}

View File

@ -34,6 +34,11 @@ import java.util.Collections;
* @since 1.4
*/
public final class FieldAttributes {
// TODO(Joel): Fix how we configure this cache in a follow-up CL.
private static final Cache<Pair<Class<?>, String>, Collection<Annotation>> ANNOTATION_CACHE =
new LruCache<Pair<Class<?>,String>, Collection<Annotation>>(1500);
private final Class<?> parentClazz;
private final Field field;
private final Class<?> declaredType;
private final boolean isSynthetic;
@ -49,13 +54,14 @@ public final class FieldAttributes {
*
* @param f the field to pull attributes from
*/
FieldAttributes(final Field f) {
Preconditions.checkNotNull(f);
field = f;
name = field.getName();
FieldAttributes(final Class<?> parentClazz, final Field f) {
Preconditions.checkNotNull(parentClazz);
this.parentClazz = parentClazz;
name = f.getName();
declaredType = f.getType();
isSynthetic = f.isSynthetic();
modifiers = field.getModifiers();
modifiers = f.getModifiers();
field = f;
}
/**
@ -127,8 +133,13 @@ public final class FieldAttributes {
*/
public Collection<Annotation> getAnnotations() {
if (annotations == null) {
annotations = Collections.unmodifiableCollection(
Arrays.asList(field.getAnnotations()));
Pair<Class<?>, String> key = new Pair<Class<?>, String>(parentClazz, name);
annotations = ANNOTATION_CACHE.getElement(key);
if (annotations == null) {
annotations = Collections.unmodifiableCollection(
Arrays.asList(field.getAnnotations()));
ANNOTATION_CACHE.addElement(key, annotations);
}
}
return annotations;
}

View File

@ -17,7 +17,7 @@
package com.google.gson;
/**
* A {@link FieldNamingStrategy} that ensures the JSON field names consist of only
* A {@link FieldNamingStrategy2} that ensures the JSON field names consist of only
* lower case letters and are separated by a particular {@code separatorString}.
*
*<p>The following is an example:</p>

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed 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 com.google.gson;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* An implementation of the {@link Cache} interface that evict objects from the cache using an
* LRU (least recently used) algorithm. Object start getting evicted from the cache once the
* {@code maxCapacity} is reached.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
class LruCache<K, V> extends LinkedHashMap<K, V> implements Cache<K, V> {
private static final long serialVersionUID = 1L;
private final int maxCapacity;
LruCache(int maxCapacity) {
super(maxCapacity, 0.7F, true);
this.maxCapacity = maxCapacity;
}
public void addElement(K key, V value) {
put(key, value);
}
public void clear() {
super.clear();
}
public V getElement(K key) {
return get(key);
}
public V removeElement(K key) {
return remove(key);
}
public int size() {
return super.size();
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> entry) {
return size() > maxCapacity;
}
}

View File

@ -148,7 +148,7 @@ final class ObjectNavigator {
Field[] fields = clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (Field f : fields) {
FieldAttributes fieldAttributes = new FieldAttributes(f);
FieldAttributes fieldAttributes = new FieldAttributes(clazz, f);
if (exclusionStrategy.shouldSkipField(fieldAttributes)
|| exclusionStrategy.shouldSkipClass(fieldAttributes.getDeclaredClass())) {
continue; // skip

View File

@ -31,7 +31,7 @@ public class DisjunctionExclusionStrategyTest extends TestCase {
private static final ExclusionStrategy FALSE_STRATEGY = new MockExclusionStrategy(false, false);
private static final ExclusionStrategy TRUE_STRATEGY = new MockExclusionStrategy(true, true);
private static final Class<?> CLAZZ = String.class;
private static final FieldAttributes FIELD = new FieldAttributes(CLAZZ.getFields()[0]);
private static final FieldAttributes FIELD = new FieldAttributes(CLAZZ, CLAZZ.getFields()[0]);
public void testBadInstantiation() throws Exception {
try {

View File

@ -42,22 +42,22 @@ public class ExposeAnnotationDeserializationExclusionStrategyTest extends TestCa
public void testSkipNonAnnotatedFields() throws Exception {
Field f = MockObject.class.getField("hiddenField");
assertTrue(strategy.shouldSkipField(new FieldAttributes(f)));
assertTrue(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
public void testSkipExplicitlySkippedFields() throws Exception {
Field f = MockObject.class.getField("explicitlyHiddenField");
assertTrue(strategy.shouldSkipField(new FieldAttributes(f)));
assertTrue(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
public void testNeverSkipExposedAnnotatedFields() throws Exception {
Field f = MockObject.class.getField("exposedField");
assertFalse(strategy.shouldSkipField(new FieldAttributes(f)));
assertFalse(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
public void testNeverSkipExplicitlyExposedAnnotatedFields() throws Exception {
Field f = MockObject.class.getField("explicitlyExposedField");
assertFalse(strategy.shouldSkipField(new FieldAttributes(f)));
assertFalse(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
@SuppressWarnings("unused")

View File

@ -42,22 +42,22 @@ public class ExposeAnnotationSerializationExclusionStrategyTest extends TestCase
public void testSkipNonAnnotatedFields() throws Exception {
Field f = MockObject.class.getField("hiddenField");
assertTrue(strategy.shouldSkipField(new FieldAttributes(f)));
assertTrue(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
public void testSkipExplicitlySkippedFields() throws Exception {
Field f = MockObject.class.getField("explicitlyHiddenField");
assertTrue(strategy.shouldSkipField(new FieldAttributes(f)));
assertTrue(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
public void testNeverSkipExposedAnnotatedFields() throws Exception {
Field f = MockObject.class.getField("exposedField");
assertFalse(strategy.shouldSkipField(new FieldAttributes(f)));
assertFalse(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
public void testNeverSkipExplicitlyExposedAnnotatedFields() throws Exception {
Field f = MockObject.class.getField("explicitlyExposedField");
assertFalse(strategy.shouldSkipField(new FieldAttributes(f)));
assertFalse(strategy.shouldSkipField(new FieldAttributes(MockObject.class, f)));
}
@SuppressWarnings("unused")

View File

@ -36,14 +36,14 @@ public class FieldAttributesTest extends TestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
fieldAttributes = new FieldAttributes(Foo.class.getField("bar"));
fieldAttributes = new FieldAttributes(Foo.class, Foo.class.getField("bar"));
}
public void testNullField() throws Exception {
try {
new FieldAttributes(null);
new FieldAttributes(Foo.class, null);
fail("Field parameter can not be null");
} catch (IllegalArgumentException expected) { }
} catch (NullPointerException expected) { }
}
public void testModifiers() throws Exception {

View File

@ -46,7 +46,7 @@ public class InnerClassExclusionStrategyTest extends TestCase {
public void testExcludeInnerClassField() throws Exception {
Field f = getClass().getField("innerClass");
assertTrue(strategy.shouldSkipField(new FieldAttributes(f)));
assertTrue(strategy.shouldSkipField(new FieldAttributes(getClass(), f)));
}
public void testIncludeStaticNestedClassObject() throws Exception {
@ -56,7 +56,7 @@ public class InnerClassExclusionStrategyTest extends TestCase {
public void testIncludeStaticNestedClassField() throws Exception {
Field f = getClass().getField("staticNestedClass");
assertFalse(strategy.shouldSkipField(new FieldAttributes(f)));
assertFalse(strategy.shouldSkipField(new FieldAttributes(getClass(), f)));
}
class InnerClass {

View File

@ -34,7 +34,7 @@ public class JavaFieldNamingPolicyTest extends TestCase {
}
public void testFieldNamingPolicy() throws Exception {
FieldAttributes f = new FieldAttributes(String.class.getFields()[0]);
FieldAttributes f = new FieldAttributes(String.class, String.class.getFields()[0]);
assertEquals(f.getName(), namingPolicy.translateName(f));
}

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed 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 com.google.gson;
import junit.framework.TestCase;
/**
* Unit test for the {@link LruCache} class.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public class LruCacheTest extends TestCase {
public void testCacheHitAndMiss() throws Exception {
Cache<String, Integer> cache = new LruCache<String, Integer>(3);
String key = "key1";
assertNull(cache.getElement(key));
cache.addElement(key, 1);
assertEquals(1, cache.getElement(key).intValue());
String key2 = "key2";
cache.addElement(key2, 2);
assertEquals(1, cache.getElement(key).intValue());
assertEquals(2, cache.getElement(key2).intValue());
}
public void testCacheKeyOverwrite() throws Exception {
Cache<String, Integer> cache = new LruCache<String, Integer>(3);
String key = "key1";
assertNull(cache.getElement(key));
cache.addElement(key, 1);
assertEquals(1, cache.getElement(key).intValue());
cache.addElement(key, 5);
assertEquals(5, cache.getElement(key).intValue());
}
public void testCacheEviction() throws Exception {
Cache<String, Integer> cache = new LruCache<String, Integer>(3);
cache.addElement("key1", 1);
cache.addElement("key2", 2);
cache.addElement("key3", 3);
assertEquals(1, cache.getElement("key1").intValue());
assertEquals(2, cache.getElement("key2").intValue());
assertEquals(3, cache.getElement("key3").intValue());
// Access key1 to show key2 will be evicted (shows not a FIFO cache)
cache.getElement("key1");
cache.addElement("key4", 4);
assertEquals(1, cache.getElement("key1").intValue());
assertNull(cache.getElement("key2"));
assertEquals(3, cache.getElement("key3").intValue());
assertEquals(4, cache.getElement("key4").intValue());
}
}

View File

@ -38,6 +38,6 @@ public class NullExclusionStrategyTest extends TestCase {
public void testNeverSkipsField() throws Exception {
assertFalse(strategy.shouldSkipField(
new FieldAttributes(String.class.getFields()[0])));
new FieldAttributes(String.class, String.class.getFields()[0])));
}
}

View File

@ -38,7 +38,8 @@ public class SerializedNameAnnotationInterceptingNamingPolicyTest extends TestCa
public void testFieldWithAnnotation() throws Exception {
String fieldName = "fieldWithAnnotation";
FieldAttributes f = new FieldAttributes(SomeObject.class.getField(fieldName));
FieldAttributes f = new FieldAttributes(
SomeObject.class, SomeObject.class.getField(fieldName));
assertFalse(ANNOTATED_FIELD_NAME.equals(fieldName));
assertEquals(ANNOTATED_FIELD_NAME, policy.translateName(f));
@ -46,7 +47,8 @@ public class SerializedNameAnnotationInterceptingNamingPolicyTest extends TestCa
public void testFieldWithoutAnnotation() throws Exception {
String fieldName = "fieldWithoutAnnotation";
FieldAttributes f = new FieldAttributes(SomeObject.class.getField(fieldName));
FieldAttributes f = new FieldAttributes(
SomeObject.class, SomeObject.class.getField(fieldName));
assertEquals(fieldName, policy.translateName(f));
}

View File

@ -43,7 +43,7 @@ public class VersionExclusionStrategyTest extends TestCase {
VersionExclusionStrategy strategy = new VersionExclusionStrategy(VERSION);
assertFalse(strategy.shouldSkipClass(clazz));
assertFalse(strategy.shouldSkipField(new FieldAttributes(f)));
assertFalse(strategy.shouldSkipField(new FieldAttributes(clazz, f)));
}
public void testClassAndFieldAreBehindInVersion() throws Exception {
@ -52,7 +52,7 @@ public class VersionExclusionStrategyTest extends TestCase {
VersionExclusionStrategy strategy = new VersionExclusionStrategy(VERSION + 1);
assertFalse(strategy.shouldSkipClass(clazz));
assertFalse(strategy.shouldSkipField(new FieldAttributes(f)));
assertFalse(strategy.shouldSkipField(new FieldAttributes(clazz, f)));
}
public void testClassAndFieldAreAheadInVersion() throws Exception {
@ -61,7 +61,7 @@ public class VersionExclusionStrategyTest extends TestCase {
VersionExclusionStrategy strategy = new VersionExclusionStrategy(VERSION - 1);
assertTrue(strategy.shouldSkipClass(clazz));
assertTrue(strategy.shouldSkipField(new FieldAttributes(f)));
assertTrue(strategy.shouldSkipField(new FieldAttributes(clazz, f)));
}
@Since(VERSION)