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:
parent
e6496fddd6
commit
9816426bba
61
gson/src/main/java/com/google/gson/Cache.java
Normal file
61
gson/src/main/java/com/google/gson/Cache.java
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
64
gson/src/main/java/com/google/gson/LruCache.java
Normal file
64
gson/src/main/java/com/google/gson/LruCache.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
73
gson/src/test/java/com/google/gson/LruCacheTest.java
Normal file
73
gson/src/test/java/com/google/gson/LruCacheTest.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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])));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user