diff --git a/gson/src/main/java/com/google/gson/FieldAttributes.java b/gson/src/main/java/com/google/gson/FieldAttributes.java index a4188db5..480c2b70 100644 --- a/gson/src/main/java/com/google/gson/FieldAttributes.java +++ b/gson/src/main/java/com/google/gson/FieldAttributes.java @@ -16,13 +16,12 @@ package com.google.gson; -import com.google.gson.internal.Pair; +import com.google.gson.internal.$Gson$Preconditions; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; /** * A data object that stores attributes of a field. @@ -35,22 +34,7 @@ import java.util.Collections; * @since 1.4 */ public final class FieldAttributes { - private static final String MAX_CACHE_PROPERTY_NAME = - "com.google.gson.annotation_cache_size_hint"; - - private static final LruCache, String>, Collection> ANNOTATION_CACHE - = new LruCache,String>, Collection>(getMaxCacheSize()); - - private final Class declaringClazz; private final Field field; - private final Class declaredType; - private final boolean isSynthetic; - private final int modifiers; - private final String name; - - // Fields used for lazy initialization - private Type genericType; - private Collection annotations; /** * Constructs a Field Attributes object from the {@code f}. @@ -58,37 +42,22 @@ public final class FieldAttributes { * @param f the field to pull attributes from */ public FieldAttributes(Field f) { - this.declaringClazz = f.getDeclaringClass(); - this.name = f.getName(); - this.declaredType = f.getType(); - this.isSynthetic = f.isSynthetic(); - this.modifiers = f.getModifiers(); + $Gson$Preconditions.checkNotNull(f); this.field = f; } - private static int getMaxCacheSize() { - final int defaultMaxCacheSize = 2000; - try { - String propertyValue = System.getProperty( - MAX_CACHE_PROPERTY_NAME, String.valueOf(defaultMaxCacheSize)); - return Integer.parseInt(propertyValue); - } catch (NumberFormatException e) { - return defaultMaxCacheSize; - } - } - /** * @return the declaring class that contains this field */ public Class getDeclaringClass() { - return declaringClazz; + return field.getDeclaringClass(); } /** * @return the name of the field */ public String getName() { - return name; + return field.getName(); } /** @@ -108,10 +77,7 @@ public final class FieldAttributes { * @return the specific type declared for this field */ public Type getDeclaredType() { - if (genericType == null) { - genericType = field.getGenericType(); - } - return genericType; + return field.getGenericType(); } /** @@ -131,7 +97,7 @@ public final class FieldAttributes { * @return the specific class object that was declared for the field */ public Class getDeclaredClass() { - return declaredType; + return field.getType(); } /** @@ -142,7 +108,7 @@ public final class FieldAttributes { * @return the annotation instance if it is bound to the field; otherwise {@code null} */ public T getAnnotation(Class annotation) { - return getAnnotationFromArray(getAnnotations(), annotation); + return field.getAnnotation(annotation); } /** @@ -152,17 +118,7 @@ public final class FieldAttributes { * @since 1.4 */ public Collection getAnnotations() { - if (annotations == null) { - Pair, String> key = new Pair, String>(declaringClazz, name); - Collection cachedValue = ANNOTATION_CACHE.get(key); - if (cachedValue == null) { - cachedValue = Collections.unmodifiableCollection( - Arrays.asList(field.getAnnotations())); - ANNOTATION_CACHE.put(key, cachedValue); - } - annotations = cachedValue; - } - return annotations; + return Arrays.asList(field.getAnnotations()); } /** @@ -176,7 +132,7 @@ public final class FieldAttributes { * @see java.lang.reflect.Modifier */ public boolean hasModifier(int modifier) { - return (modifiers & modifier) != 0; + return (field.getModifiers() & modifier) != 0; } /** @@ -196,7 +152,7 @@ public final class FieldAttributes { * @return true if the field is synthetic; otherwise false */ boolean isSynthetic() { - return isSynthetic; + return field.isSynthetic(); } @SuppressWarnings("unchecked") diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 36cad61f..a0effc09 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -17,6 +17,7 @@ package com.google.gson; import com.google.gson.internal.ConstructorConstructor; +import com.google.gson.internal.Excluder; import com.google.gson.internal.Primitives; import com.google.gson.internal.Streams; import com.google.gson.internal.TypeMap; @@ -25,7 +26,6 @@ import com.google.gson.internal.bind.BigDecimalTypeAdapter; import com.google.gson.internal.bind.BigIntegerTypeAdapter; import com.google.gson.internal.bind.CollectionTypeAdapterFactory; import com.google.gson.internal.bind.DateTypeAdapter; -import com.google.gson.internal.bind.ExcludedTypeAdapterFactory; import com.google.gson.internal.bind.JsonElementReader; import com.google.gson.internal.bind.JsonElementWriter; import com.google.gson.internal.bind.MapTypeAdapterFactory; @@ -45,7 +45,6 @@ import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; -import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; @@ -103,10 +102,6 @@ public final class Gson { static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; - private static final ExclusionStrategy DEFAULT_EXCLUSION_STRATEGY = new GsonExclusionStrategy( - GsonExclusionStrategy.IGNORE_VERSIONS, Modifier.TRANSIENT | Modifier.STATIC, - true, true, true, false, false); - private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; /** @@ -188,15 +183,13 @@ public final class Gson { */ @SuppressWarnings("unchecked") public Gson() { - this(DEFAULT_EXCLUSION_STRATEGY, DEFAULT_EXCLUSION_STRATEGY, FieldNamingPolicy.IDENTITY, + this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY, EMPTY_MAP, false, EMPTY_MAP, EMPTY_MAP, false, DEFAULT_JSON_NON_EXECUTABLE, true, false, false, LongSerializationPolicy.DEFAULT, Collections.emptyList()); } - Gson(final ExclusionStrategy deserializationExclusionStrategy, - final ExclusionStrategy serializationExclusionStrategy, - final FieldNamingStrategy fieldNamingPolicy, + Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy, final TypeMap> instanceCreators, boolean serializeNulls, final TypeMap> serializers, final TypeMap> deserializers, @@ -213,8 +206,7 @@ public final class Gson { this.prettyPrinting = prettyPrinting; TypeAdapter.Factory reflectiveTypeAdapterFactory = new ReflectiveTypeAdapterFactory( - constructorConstructor, fieldNamingPolicy, serializationExclusionStrategy, - deserializationExclusionStrategy); + constructorConstructor, fieldNamingPolicy, excluder); ConstructorConstructor constructorConstructor = new ConstructorConstructor(); List factories = new ArrayList(); @@ -229,8 +221,7 @@ public final class Gson { doubleAdapter(serializeSpecialFloatingPointValues))); factories.add(TypeAdapters.newFactory(float.class, Float.class, floatAdapter(serializeSpecialFloatingPointValues))); - factories.add(new ExcludedTypeAdapterFactory( - serializationExclusionStrategy, deserializationExclusionStrategy)); + factories.add(excluder); factories.add(TypeAdapters.NUMBER_FACTORY); factories.add(TypeAdapters.CHARACTER_FACTORY); factories.add(TypeAdapters.STRING_BUILDER_FACTORY); diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index 1094b4a1..76d6af8d 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -17,11 +17,11 @@ package com.google.gson; import com.google.gson.internal.$Gson$Preconditions; +import com.google.gson.internal.Excluder; import com.google.gson.internal.Primitives; import com.google.gson.internal.TypeMap; import com.google.gson.internal.bind.TypeAdapters; import com.google.gson.reflect.TypeToken; -import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.sql.Timestamp; import java.text.DateFormat; @@ -63,12 +63,7 @@ import java.util.List; * @author Joel Leitch */ public final class GsonBuilder { - private ExclusionStrategy serializeExclusionStrategy; - private ExclusionStrategy deserializeExclusionStrategy; - private int modifiers = Modifier.TRANSIENT | Modifier.STATIC; - private double ignoreVersionsAfter = GsonExclusionStrategy.IGNORE_VERSIONS; - private boolean serializeInnerClasses = true; - private boolean excludeFieldsWithoutExposeAnnotation = false; + private Excluder excluder = Excluder.DEFAULT; private LongSerializationPolicy longSerializationPolicy; private FieldNamingStrategy fieldNamingPolicy; @@ -142,7 +137,7 @@ public final class GsonBuilder { * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern */ public GsonBuilder setVersion(double ignoreVersionsAfter) { - this.ignoreVersionsAfter = ignoreVersionsAfter; + excluder = excluder.withVersion(ignoreVersionsAfter); return this; } @@ -158,10 +153,7 @@ public final class GsonBuilder { * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern */ public GsonBuilder excludeFieldsWithModifiers(int... modifiers) { - this.modifiers = 0; - for (int modifier : modifiers) { - this.modifiers |= modifier; - } + excluder = excluder.withModifiers(modifiers); return this; } @@ -186,7 +178,7 @@ public final class GsonBuilder { * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern */ public GsonBuilder excludeFieldsWithoutExposeAnnotation() { - excludeFieldsWithoutExposeAnnotation = true; + excluder = excluder.excludeFieldsWithoutExposeAnnotation(); return this; } @@ -290,7 +282,7 @@ public final class GsonBuilder { * @since 1.3 */ public GsonBuilder disableInnerClassSerialization() { - serializeInnerClasses = false; + excluder = excluder.disableInnerClassSerialization(); return this; } @@ -345,8 +337,7 @@ public final class GsonBuilder { */ public GsonBuilder setExclusionStrategies(ExclusionStrategy... strategies) { for (ExclusionStrategy strategy : strategies) { - addSerializationExclusionStrategy(strategy); - addDeserializationExclusionStrategy(strategy); + excluder = excluder.withExclusionStrategy(strategy, true, true); } return this; } @@ -364,7 +355,7 @@ public final class GsonBuilder { * @since 1.7 */ public GsonBuilder addSerializationExclusionStrategy(ExclusionStrategy strategy) { - serializeExclusionStrategy = combine(serializeExclusionStrategy, strategy); + excluder = excluder.withExclusionStrategy(strategy, true, false); return this; } @@ -381,7 +372,7 @@ public final class GsonBuilder { * @since 1.7 */ public GsonBuilder addDeserializationExclusionStrategy(ExclusionStrategy strategy) { - deserializeExclusionStrategy = combine(deserializeExclusionStrategy, strategy); + excluder = excluder.withExclusionStrategy(strategy, false, true); return this; } @@ -600,27 +591,6 @@ public final class GsonBuilder { return this; } - /** - * Unions two exclusion strategies. If the first is null, this returns the - * second. - */ - private static ExclusionStrategy combine(final ExclusionStrategy a, final ExclusionStrategy b) { - if (b == null) { - throw new IllegalArgumentException(); - } - if (a == null) { - return b; - } - return new ExclusionStrategy() { - public boolean shouldSkipField(FieldAttributes f) { - return a.shouldSkipField(f) || b.shouldSkipField(f); - } - public boolean shouldSkipClass(Class clazz) { - return a.shouldSkipClass(clazz) || b.shouldSkipClass(clazz); - } - }; - } - /** * Creates a {@link Gson} instance based on the current configuration. This method is free of * side-effects to this {@code GsonBuilder} instance and hence can be called multiple times. @@ -630,17 +600,10 @@ public final class GsonBuilder { public Gson create() { addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, serializers, deserializers); - ExclusionStrategy deserializeExclusionStrategy = combine(this.deserializeExclusionStrategy, - new GsonExclusionStrategy(ignoreVersionsAfter, modifiers, true, - true, serializeInnerClasses, false, excludeFieldsWithoutExposeAnnotation)); - ExclusionStrategy serializeExclusionStrategy = combine(this.serializeExclusionStrategy, - new GsonExclusionStrategy(ignoreVersionsAfter, modifiers, true, true, - serializeInnerClasses, excludeFieldsWithoutExposeAnnotation, false)); - - return new Gson(deserializeExclusionStrategy, serializeExclusionStrategy, fieldNamingPolicy, - instanceCreators.copyOf().makeUnmodifiable(), serializeNulls, - serializers.copyOf().makeUnmodifiable(), deserializers.copyOf().makeUnmodifiable(), - complexMapKeySerialization, generateNonExecutableJson, escapeHtmlChars, prettyPrinting, + return new Gson(excluder, fieldNamingPolicy, instanceCreators.copyOf().makeUnmodifiable(), + serializeNulls, serializers.copyOf().makeUnmodifiable(), + deserializers.copyOf().makeUnmodifiable(), complexMapKeySerialization, + generateNonExecutableJson, escapeHtmlChars, prettyPrinting, serializeSpecialFloatingPointValues, longSerializationPolicy, typeAdapterFactories); } diff --git a/gson/src/main/java/com/google/gson/GsonExclusionStrategy.java b/gson/src/main/java/com/google/gson/GsonExclusionStrategy.java deleted file mode 100644 index 974e0b81..00000000 --- a/gson/src/main/java/com/google/gson/GsonExclusionStrategy.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2008 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 com.google.gson.annotations.Expose; -import com.google.gson.annotations.Since; -import com.google.gson.annotations.Until; -import java.lang.reflect.Modifier; - -/** - * A configurable exclusion strategy. This strategy supports version attributes - * {@link Since} and {@link Until}, modifiers, synthetic fields, anonymous and - * local classes, inner classes, and fields with the {@link Expose} annotation. - * - * @author Joel Leitch - * @author Jesse Wilson - */ -final class GsonExclusionStrategy implements ExclusionStrategy { - static final double IGNORE_VERSIONS = -1D; - private final double version; - private final int modifiers; - private final boolean excludeSyntheticFields; - private final boolean excludeAnonymousAndLocal; - private final boolean serializeInnerClasses; - private final boolean requireExposeOnSerialize; - private final boolean requireExposeOnDeserialize; - - GsonExclusionStrategy(double version, int modifiers, boolean excludeSyntheticFields, - boolean excludeAnonymousAndLocal, boolean serializeInnerClasses, - boolean requireExposeOnSerialize, boolean requireExposeOnDeserialize) { - this.version = version; - this.modifiers = modifiers; - this.excludeSyntheticFields = excludeSyntheticFields; - this.excludeAnonymousAndLocal = excludeAnonymousAndLocal; - this.serializeInnerClasses = serializeInnerClasses; - this.requireExposeOnSerialize = requireExposeOnSerialize; - this.requireExposeOnDeserialize = requireExposeOnDeserialize; - } - - public boolean shouldSkipField(FieldAttributes f) { - if (f.hasModifier(modifiers)) { - return true; - } - if (version != GsonExclusionStrategy.IGNORE_VERSIONS - && !isValidVersion(f.getAnnotation(Since.class), f.getAnnotation(Until.class))) { - return true; - } - if (excludeSyntheticFields && f.isSynthetic()) { - return true; - } - if (requireExposeOnSerialize || requireExposeOnDeserialize) { - Expose annotation = f.getAnnotation(Expose.class); - if (annotation == null - || requireExposeOnSerialize && !annotation.serialize() - || requireExposeOnDeserialize && !annotation.deserialize()) { - return true; - } - } - if (!serializeInnerClasses && isInnerClass(f.getDeclaredClass())) { - return true; - } - if (excludeAnonymousAndLocal && isAnonymousOrLocal(f.getDeclaredClass())) { - return true; - } - return false; - } - - public boolean shouldSkipClass(Class clazz) { - if (version != GsonExclusionStrategy.IGNORE_VERSIONS - && !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) { - return true; - } - if (!serializeInnerClasses && isInnerClass(clazz)) { - return true; - } - if (excludeAnonymousAndLocal && isAnonymousOrLocal(clazz)) { - return true; - } - return false; - } - - private boolean isAnonymousOrLocal(Class clazz) { - return !Enum.class.isAssignableFrom(clazz) - && (clazz.isAnonymousClass() || clazz.isLocalClass()); - } - - private boolean isInnerClass(Class clazz) { - return clazz.isMemberClass() && !isStatic(clazz); - } - - private boolean isStatic(Class clazz) { - return (clazz.getModifiers() & Modifier.STATIC) != 0; - } - - private boolean isValidVersion(Since since, Until until) { - return isValidSince(since) && isValidUntil(until); - } - - private boolean isValidSince(Since annotation) { - if (annotation != null) { - double annotationVersion = annotation.value(); - if (annotationVersion > version) { - return false; - } - } - return true; - } - - private boolean isValidUntil(Until annotation) { - if (annotation != null) { - double annotationVersion = annotation.value(); - if (annotationVersion <= version) { - return false; - } - } - return true; - } -} diff --git a/gson/src/main/java/com/google/gson/internal/Excluder.java b/gson/src/main/java/com/google/gson/internal/Excluder.java new file mode 100644 index 00000000..25d30d1f --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/Excluder.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2008 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.internal; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.Since; +import com.google.gson.annotations.Until; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This class selects which fields and types to omit. It is configurable, + * supporting version attributes {@link Since} and {@link Until}, modifiers, + * synthetic fields, anonymous and local classes, inner classes, and fields with + * the {@link Expose} annotation. + * + *

This class is a type adapter factory; types that are excluded will be + * adapted to null. It may delegate to another type adapter if only one + * direction is excluded. + * + * @author Joel Leitch + * @author Jesse Wilson + */ +public final class Excluder implements TypeAdapter.Factory, Cloneable { + private static final double IGNORE_VERSIONS = -1.0d; + public static final Excluder DEFAULT = new Excluder(); + + private double version = IGNORE_VERSIONS; + private int modifiers = Modifier.TRANSIENT | Modifier.STATIC; + private boolean serializeInnerClasses = true; + private boolean requireExpose; + private List serializationStrategies = Collections.emptyList(); + private List deserializationStrategies = Collections.emptyList(); + + @Override protected Excluder clone() { + try { + return (Excluder) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + public Excluder withVersion(double ignoreVersionsAfter) { + Excluder result = clone(); + result.version = ignoreVersionsAfter; + return result; + } + + public Excluder withModifiers(int... modifiers) { + Excluder result = clone(); + result.modifiers = 0; + for (int modifier : modifiers) { + result.modifiers |= modifier; + } + return result; + } + + public Excluder disableInnerClassSerialization() { + Excluder result = clone(); + result.serializeInnerClasses = false; + return result; + } + + public Excluder excludeFieldsWithoutExposeAnnotation() { + Excluder result = clone(); + result.requireExpose = true; + return result; + } + + public Excluder withExclusionStrategy(ExclusionStrategy exclusionStrategy, + boolean serialization, boolean deserialization) { + Excluder result = clone(); + if (serialization) { + result.serializationStrategies = new ArrayList(serializationStrategies); + result.serializationStrategies.add(exclusionStrategy); + } + if (deserialization) { + result.deserializationStrategies + = new ArrayList(deserializationStrategies); + result.deserializationStrategies.add(exclusionStrategy); + } + return result; + } + + public TypeAdapter create(final Gson context, final TypeToken type) { + Class rawType = type.getRawType(); + final boolean skipSerialize = excludeClass(rawType, true); + final boolean skipDeserialize = excludeClass(rawType, false); + + if (!skipSerialize && !skipDeserialize) { + return null; + } + + return new TypeAdapter() { + /** The delegate is lazily created because it may not be needed, and creating it may fail. */ + private TypeAdapter delegate; + + @Override public T read(JsonReader reader) throws IOException { + if (skipDeserialize) { + reader.skipValue(); + return null; + } + return delegate().read(reader); + } + + @Override public void write(JsonWriter writer, T value) throws IOException { + if (skipSerialize) { + writer.nullValue(); + return; + } + delegate().write(writer, value); + } + + private TypeAdapter delegate() { + TypeAdapter d = delegate; + return d != null + ? d + : (delegate = context.getNextAdapter(Excluder.this, type)); + } + }; + } + + public boolean excludeField(Field field, boolean serialize) { + if ((modifiers & field.getModifiers()) != 0) { + return true; + } + + if (version != Excluder.IGNORE_VERSIONS + && !isValidVersion(field.getAnnotation(Since.class), field.getAnnotation(Until.class))) { + return true; + } + + if (field.isSynthetic()) { + return true; + } + + if (requireExpose) { + Expose annotation = field.getAnnotation(Expose.class); + if (annotation == null || (serialize ? !annotation.serialize() : !annotation.deserialize())) { + return true; + } + } + + if (!serializeInnerClasses && isInnerClass(field.getType())) { + return true; + } + + if (isAnonymousOrLocal(field.getType())) { + return true; + } + + List list = serialize ? serializationStrategies : deserializationStrategies; + if (!list.isEmpty()) { + FieldAttributes fieldAttributes = new FieldAttributes(field); + for (ExclusionStrategy exclusionStrategy : list) { + if (exclusionStrategy.shouldSkipField(fieldAttributes)) { + return true; + } + } + } + + return false; + } + + public boolean excludeClass(Class clazz, boolean serialize) { + if (version != Excluder.IGNORE_VERSIONS + && !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) { + return true; + } + + if (!serializeInnerClasses && isInnerClass(clazz)) { + return true; + } + + if (isAnonymousOrLocal(clazz)) { + return true; + } + + List list = serialize ? serializationStrategies : deserializationStrategies; + for (ExclusionStrategy exclusionStrategy : list) { + if (exclusionStrategy.shouldSkipClass(clazz)) { + return true; + } + } + + return false; + } + + private boolean isAnonymousOrLocal(Class clazz) { + return !Enum.class.isAssignableFrom(clazz) + && (clazz.isAnonymousClass() || clazz.isLocalClass()); + } + + private boolean isInnerClass(Class clazz) { + return clazz.isMemberClass() && !isStatic(clazz); + } + + private boolean isStatic(Class clazz) { + return (clazz.getModifiers() & Modifier.STATIC) != 0; + } + + private boolean isValidVersion(Since since, Until until) { + return isValidSince(since) && isValidUntil(until); + } + + private boolean isValidSince(Since annotation) { + if (annotation != null) { + double annotationVersion = annotation.value(); + if (annotationVersion > version) { + return false; + } + } + return true; + } + + private boolean isValidUntil(Until annotation) { + if (annotation != null) { + double annotationVersion = annotation.value(); + if (annotationVersion <= version) { + return false; + } + } + return true; + } +} diff --git a/gson/src/main/java/com/google/gson/internal/bind/ExcludedTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ExcludedTypeAdapterFactory.java deleted file mode 100644 index 3eb8b2fc..00000000 --- a/gson/src/main/java/com/google/gson/internal/bind/ExcludedTypeAdapterFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2011 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.internal.bind; - -import com.google.gson.ExclusionStrategy; -import com.google.gson.Gson; -import com.google.gson.TypeAdapter; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import java.io.IOException; - -/** - * This type adapter skips values using an exclusion strategy. It may delegate - * to another type adapter if only one direction is excluded. - */ -public final class ExcludedTypeAdapterFactory implements TypeAdapter.Factory { - private final ExclusionStrategy serializationExclusionStrategy; - private final ExclusionStrategy deserializationExclusionStrategy; - - public ExcludedTypeAdapterFactory(ExclusionStrategy serializationExclusionStrategy, - ExclusionStrategy deserializationExclusionStrategy) { - this.serializationExclusionStrategy = serializationExclusionStrategy; - this.deserializationExclusionStrategy = deserializationExclusionStrategy; - } - - public TypeAdapter create(final Gson context, final TypeToken type) { - Class rawType = type.getRawType(); - final boolean skipSerialize = serializationExclusionStrategy.shouldSkipClass(rawType); - final boolean skipDeserialize = deserializationExclusionStrategy.shouldSkipClass(rawType); - - if (!skipSerialize && !skipDeserialize) { - return null; - } - - return new TypeAdapter() { - /** The delegate is lazily created because it may not be needed, and creating it may fail. */ - private TypeAdapter delegate; - - @Override public T read(JsonReader reader) throws IOException { - if (skipDeserialize) { - reader.skipValue(); - return null; - } - return delegate().read(reader); - } - - @Override public void write(JsonWriter writer, T value) throws IOException { - if (skipSerialize) { - writer.nullValue(); - return; - } - delegate().write(writer, value); - } - - private TypeAdapter delegate() { - TypeAdapter d = delegate; - return d != null - ? d - : (delegate = context.getNextAdapter(ExcludedTypeAdapterFactory.this, type)); - } - }; - } -} diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java index a1913ca7..0459eff0 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -16,8 +16,6 @@ package com.google.gson.internal.bind; -import com.google.gson.ExclusionStrategy; -import com.google.gson.FieldAttributes; import com.google.gson.FieldNamingStrategy; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; @@ -25,6 +23,7 @@ import com.google.gson.TypeAdapter; import com.google.gson.annotations.SerializedName; import com.google.gson.internal.$Gson$Types; import com.google.gson.internal.ConstructorConstructor; +import com.google.gson.internal.Excluder; import com.google.gson.internal.ObjectConstructor; import com.google.gson.internal.Primitives; import com.google.gson.reflect.TypeToken; @@ -44,26 +43,17 @@ import java.util.Map; public final class ReflectiveTypeAdapterFactory implements TypeAdapter.Factory { private final ConstructorConstructor constructorConstructor; private final FieldNamingStrategy fieldNamingPolicy; - private final ExclusionStrategy serializationExclusionStrategy; - private final ExclusionStrategy deserializationExclusionStrategy; + private final Excluder excluder; public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, - FieldNamingStrategy fieldNamingPolicy, ExclusionStrategy serializationExclusionStrategy, - ExclusionStrategy deserializationExclusionStrategy) { + FieldNamingStrategy fieldNamingPolicy, Excluder excluder) { this.constructorConstructor = constructorConstructor; this.fieldNamingPolicy = fieldNamingPolicy; - this.serializationExclusionStrategy = serializationExclusionStrategy; - this.deserializationExclusionStrategy = deserializationExclusionStrategy; + this.excluder = excluder; } - public boolean serializeField(Field f) { - return !serializationExclusionStrategy.shouldSkipClass(f.getType()) - && !serializationExclusionStrategy.shouldSkipField(new FieldAttributes(f)); - } - - private boolean deserializeField(Field f) { - return !deserializationExclusionStrategy.shouldSkipClass(f.getType()) - && !deserializationExclusionStrategy.shouldSkipField(new FieldAttributes(f)); + public boolean excludeField(Field f, boolean serialize) { + return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize); } private String getFieldName(Field f) { @@ -119,8 +109,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapter.Factory { Field[] fields = raw.getDeclaredFields(); AccessibleObject.setAccessible(fields, true); for (Field field : fields) { - boolean serialize = serializeField(field); - boolean deserialize = deserializeField(field); + boolean serialize = excludeField(field, true); + boolean deserialize = excludeField(field, false); if (!serialize && !deserialize) { continue; } diff --git a/gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java index 496ac5bf..dd8a7a92 100644 --- a/gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java +++ b/gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java @@ -18,6 +18,7 @@ package com.google.gson; import com.google.gson.annotations.Expose; +import com.google.gson.internal.Excluder; import junit.framework.TestCase; import java.lang.reflect.Field; @@ -28,49 +29,45 @@ import java.lang.reflect.Field; * @author Joel Leitch */ public class ExposeAnnotationExclusionStrategyTest extends TestCase { - private ExclusionStrategy serializationStrategy = new GsonExclusionStrategy( - GsonExclusionStrategy.IGNORE_VERSIONS, 0, true, true, true, true, false); - private ExclusionStrategy deserializationStrategy = new GsonExclusionStrategy( - GsonExclusionStrategy.IGNORE_VERSIONS, 0, true, true, true, false, true); + private Excluder excluder = Excluder.DEFAULT.excludeFieldsWithoutExposeAnnotation(); public void testNeverSkipClasses() throws Exception { - assertFalse(deserializationStrategy.shouldSkipClass(MockObject.class)); - assertFalse(serializationStrategy.shouldSkipClass(MockObject.class)); + assertFalse(excluder.excludeClass(MockObject.class, true)); + assertFalse(excluder.excludeClass(MockObject.class, false)); } public void testSkipNonAnnotatedFields() throws Exception { - FieldAttributes f = createFieldAttributes("hiddenField"); - assertTrue(deserializationStrategy.shouldSkipField(f)); - assertTrue(serializationStrategy.shouldSkipField(f)); + Field f = createFieldAttributes("hiddenField"); + assertTrue(excluder.excludeField(f, true)); + assertTrue(excluder.excludeField(f, false)); } public void testSkipExplicitlySkippedFields() throws Exception { - FieldAttributes f = createFieldAttributes("explicitlyHiddenField"); - assertTrue(deserializationStrategy.shouldSkipField(f)); - assertTrue(serializationStrategy.shouldSkipField(f)); + Field f = createFieldAttributes("explicitlyHiddenField"); + assertTrue(excluder.excludeField(f, true)); + assertTrue(excluder.excludeField(f, false)); } public void testNeverSkipExposedAnnotatedFields() throws Exception { - FieldAttributes f = createFieldAttributes("exposedField"); - assertFalse(deserializationStrategy.shouldSkipField(f)); - assertFalse(serializationStrategy.shouldSkipField(f)); + Field f = createFieldAttributes("exposedField"); + assertFalse(excluder.excludeField(f, true)); + assertFalse(excluder.excludeField(f, false)); } public void testNeverSkipExplicitlyExposedAnnotatedFields() throws Exception { - FieldAttributes f = createFieldAttributes("explicitlyExposedField"); - assertFalse(deserializationStrategy.shouldSkipField(f)); - assertFalse(serializationStrategy.shouldSkipField(f)); + Field f = createFieldAttributes("explicitlyExposedField"); + assertFalse(excluder.excludeField(f, true)); + assertFalse(excluder.excludeField(f, false)); } public void testDifferentSerializeAndDeserializeField() throws Exception { - FieldAttributes f = createFieldAttributes("explicitlyDifferentModeField"); - assertTrue(deserializationStrategy.shouldSkipField(f)); - assertFalse(serializationStrategy.shouldSkipField(f)); + Field f = createFieldAttributes("explicitlyDifferentModeField"); + assertFalse(excluder.excludeField(f, true)); + assertTrue(excluder.excludeField(f, false)); } - private static FieldAttributes createFieldAttributes(String fieldName) throws Exception { - Field f = MockObject.class.getField(fieldName); - return new FieldAttributes(f); + private static Field createFieldAttributes(String fieldName) throws Exception { + return MockObject.class.getField(fieldName); } @SuppressWarnings("unused") diff --git a/gson/src/test/java/com/google/gson/FunctionWithInternalDependenciesTest.java b/gson/src/test/java/com/google/gson/FunctionWithInternalDependenciesTest.java deleted file mode 100644 index 43a26f6c..00000000 --- a/gson/src/test/java/com/google/gson/FunctionWithInternalDependenciesTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008 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 com.google.gson.common.TestTypes.ClassWithNoFields; -import java.lang.reflect.Modifier; -import java.util.Collections; -import junit.framework.TestCase; - -/** - * Functional tests for Gson that depend on some internal package-protected elements of - * com.google.gson package and hence must be placed in the same package. We should make every - * attempt to migrate tests out of this class. - * - * @author Inderjeet Singh - * @author Joel Leitch - */ -public class FunctionWithInternalDependenciesTest extends TestCase { - - @SuppressWarnings("unchecked") - public void testAnonymousLocalClassesSerialization() throws Exception { - ExclusionStrategy exclusionStrategy = new GsonExclusionStrategy( - GsonExclusionStrategy.IGNORE_VERSIONS, Modifier.TRANSIENT | Modifier.STATIC, - true, false, true, false, false); - Gson gson = new Gson(exclusionStrategy, exclusionStrategy, FieldNamingPolicy.IDENTITY, - Gson.EMPTY_MAP, false, Gson.EMPTY_MAP, Gson.EMPTY_MAP, false, - Gson.DEFAULT_JSON_NON_EXECUTABLE, - true, false, false, LongSerializationPolicy.DEFAULT, - Collections.emptyList()); - assertEquals("{}", gson.toJson(new ClassWithNoFields() { - // empty anonymous class - })); - } -} diff --git a/gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java index f19d7e72..86f7a622 100644 --- a/gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java +++ b/gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java @@ -16,6 +16,7 @@ package com.google.gson; +import com.google.gson.internal.Excluder; import java.lang.reflect.Field; import junit.framework.TestCase; @@ -27,27 +28,26 @@ import junit.framework.TestCase; public class InnerClassExclusionStrategyTest extends TestCase { public InnerClass innerClass = new InnerClass(); public StaticNestedClass staticNestedClass = new StaticNestedClass(); - private ExclusionStrategy strategy = new GsonExclusionStrategy( - GsonExclusionStrategy.IGNORE_VERSIONS, 0, true, false, false, false, false); + private Excluder excluder = Excluder.DEFAULT.disableInnerClassSerialization(); public void testExcludeInnerClassObject() throws Exception { Class clazz = innerClass.getClass(); - assertTrue(strategy.shouldSkipClass(clazz)); + assertTrue(excluder.excludeClass(clazz, true)); } public void testExcludeInnerClassField() throws Exception { Field f = getClass().getField("innerClass"); - assertTrue(strategy.shouldSkipField(new FieldAttributes(f))); + assertTrue(excluder.excludeField(f, true)); } public void testIncludeStaticNestedClassObject() throws Exception { Class clazz = staticNestedClass.getClass(); - assertFalse(strategy.shouldSkipClass(clazz)); + assertFalse(excluder.excludeClass(clazz, true)); } public void testIncludeStaticNestedClassField() throws Exception { Field f = getClass().getField("staticNestedClass"); - assertFalse(strategy.shouldSkipField(new FieldAttributes(f))); + assertFalse(excluder.excludeField(f, true)); } class InnerClass { diff --git a/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java index 026592b2..7b6549e6 100644 --- a/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java +++ b/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java @@ -17,11 +17,11 @@ package com.google.gson; import com.google.gson.annotations.Since; -import java.lang.reflect.Field; +import com.google.gson.internal.Excluder; import junit.framework.TestCase; /** - * Unit tests for the {@link GsonExclusionStrategy} class. + * Unit tests for the {@link Excluder} class. * * @author Joel Leitch */ @@ -29,36 +29,21 @@ public class VersionExclusionStrategyTest extends TestCase { private static final double VERSION = 5.0D; public void testClassAndFieldAreAtSameVersion() throws Exception { - Class clazz = MockObject.class; - Field f = clazz.getField("someField"); - GsonExclusionStrategy strategy = new GsonExclusionStrategy(VERSION, 0, - true, true, true, false, false); - assertFalse(strategy.shouldSkipClass(clazz)); - - FieldAttributes fieldAttributes = new FieldAttributes(f); - assertFalse(strategy.shouldSkipField(fieldAttributes)); + Excluder excluder = Excluder.DEFAULT.withVersion(VERSION); + assertFalse(excluder.excludeClass(MockObject.class, true)); + assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true)); } public void testClassAndFieldAreBehindInVersion() throws Exception { - Class clazz = MockObject.class; - Field f = clazz.getField("someField"); - GsonExclusionStrategy strategy = new GsonExclusionStrategy(VERSION + 1, 0, - true, true, true, false, false); - assertFalse(strategy.shouldSkipClass(clazz)); - - FieldAttributes fieldAttributes = new FieldAttributes(f); - assertFalse(strategy.shouldSkipField(fieldAttributes)); + Excluder excluder = Excluder.DEFAULT.withVersion(VERSION + 1); + assertFalse(excluder.excludeClass(MockObject.class, true)); + assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true)); } public void testClassAndFieldAreAheadInVersion() throws Exception { - Class clazz = MockObject.class; - Field f = clazz.getField("someField"); - GsonExclusionStrategy strategy = new GsonExclusionStrategy(VERSION - 1, 0, - true, true, true, false, false); - assertTrue(strategy.shouldSkipClass(clazz)); - - FieldAttributes fieldAttributes = new FieldAttributes(f); - assertTrue(strategy.shouldSkipField(fieldAttributes)); + Excluder excluder = Excluder.DEFAULT.withVersion(VERSION - 1); + assertTrue(excluder.excludeClass(MockObject.class, true)); + assertTrue(excluder.excludeField(MockObject.class.getField("someField"), true)); } @Since(VERSION)