1 /*
2  * Copyright (C) 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package com.google.gson.internal;
18
19 import com.google.gson.ExclusionStrategy;
20 import com.google.gson.FieldAttributes;
21 import com.google.gson.Gson;
22 import com.google.gson.TypeAdapter;
23 import com.google.gson.TypeAdapterFactory;
24 import com.google.gson.annotations.Expose;
25 import com.google.gson.annotations.Since;
26 import com.google.gson.annotations.Until;
27 import com.google.gson.reflect.TypeToken;
28 import com.google.gson.stream.JsonReader;
29 import com.google.gson.stream.JsonWriter;
30 import java.io.IOException;
31 import java.lang.reflect.Field;
32 import java.lang.reflect.Modifier;
33 import java.util.ArrayList;
34 import java.util.Collections;
35 import java.util.List;
36
37 /**
38  * This class selects which fields and types to omit. It is configurable,
39  * supporting version attributes {@link Since} and {@link Until}, modifiers,
40  * synthetic fields, anonymous and local classes, inner classes, and fields with
41  * the {@link Expose} annotation.
42  *
43  * <p>This class is a type adapter factory; types that are excluded will be
44  * adapted to null. It may delegate to another type adapter if only one
45  * direction is excluded.
46  *
47  * @author Joel Leitch
48  * @author Jesse Wilson
49  */

50 public final class Excluder implements TypeAdapterFactory, Cloneable {
51   private static final double IGNORE_VERSIONS = -1.0d;
52   public static final Excluder DEFAULT = new Excluder();
53
54   private double version = IGNORE_VERSIONS;
55   private int modifiers = Modifier.TRANSIENT | Modifier.STATIC;
56   private boolean serializeInnerClasses = true;
57   private boolean requireExpose;
58   private List<ExclusionStrategy> serializationStrategies = Collections.emptyList();
59   private List<ExclusionStrategy> deserializationStrategies = Collections.emptyList();
60
61   @Override protected Excluder clone() {
62     try {
63       return (Excluder) super.clone();
64     } catch (CloneNotSupportedException e) {
65       throw new AssertionError(e);
66     }
67   }
68
69   public Excluder withVersion(double ignoreVersionsAfter) {
70     Excluder result = clone();
71     result.version = ignoreVersionsAfter;
72     return result;
73   }
74
75   public Excluder withModifiers(int... modifiers) {
76     Excluder result = clone();
77     result.modifiers = 0;
78     for (int modifier : modifiers) {
79       result.modifiers |= modifier;
80     }
81     return result;
82   }
83
84   public Excluder disableInnerClassSerialization() {
85     Excluder result = clone();
86     result.serializeInnerClasses = false;
87     return result;
88   }
89
90   public Excluder excludeFieldsWithoutExposeAnnotation() {
91     Excluder result = clone();
92     result.requireExpose = true;
93     return result;
94   }
95
96   public Excluder withExclusionStrategy(ExclusionStrategy exclusionStrategy,
97       boolean serialization, boolean deserialization) {
98     Excluder result = clone();
99     if (serialization) {
100       result.serializationStrategies = new ArrayList<ExclusionStrategy>(serializationStrategies);
101       result.serializationStrategies.add(exclusionStrategy);
102     }
103     if (deserialization) {
104       result.deserializationStrategies
105           = new ArrayList<ExclusionStrategy>(deserializationStrategies);
106       result.deserializationStrategies.add(exclusionStrategy);
107     }
108     return result;
109   }
110
111   public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
112     Class<?> rawType = type.getRawType();
113     boolean excludeClass = excludeClassChecks(rawType);
114
115     final boolean skipSerialize = excludeClass || excludeClassInStrategy(rawType, true);
116     final boolean skipDeserialize = excludeClass ||  excludeClassInStrategy(rawType, false);
117
118     if (!skipSerialize && !skipDeserialize) {
119       return null;
120     }
121
122     return new TypeAdapter<T>() {
123       /** The delegate is lazily created because it may not be needed, and creating it may fail. */
124       private TypeAdapter<T> delegate;
125
126       @Override public T read(JsonReader in) throws IOException {
127         if (skipDeserialize) {
128           in.skipValue();
129           return null;
130         }
131         return delegate().read(in);
132       }
133
134       @Override public void write(JsonWriter out, T value) throws IOException {
135         if (skipSerialize) {
136           out.nullValue();
137           return;
138         }
139         delegate().write(out, value);
140       }
141
142       private TypeAdapter<T> delegate() {
143         TypeAdapter<T> d = delegate;
144         return d != null
145             ? d
146             : (delegate = gson.getDelegateAdapter(Excluder.this, type));
147       }
148     };
149   }
150
151   public boolean excludeField(Field field, boolean serialize) {
152     if ((modifiers & field.getModifiers()) != 0) {
153       return true;
154     }
155
156     if (version != Excluder.IGNORE_VERSIONS
157         && !isValidVersion(field.getAnnotation(Since.class), field.getAnnotation(Until.class))) {
158       return true;
159     }
160
161     if (field.isSynthetic()) {
162       return true;
163     }
164
165     if (requireExpose) {
166       Expose annotation = field.getAnnotation(Expose.class);
167       if (annotation == null || (serialize ? !annotation.serialize() : !annotation.deserialize())) {
168         return true;
169       }
170     }
171
172     if (!serializeInnerClasses && isInnerClass(field.getType())) {
173       return true;
174     }
175
176     if (isAnonymousOrLocal(field.getType())) {
177       return true;
178     }
179
180     List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
181     if (!list.isEmpty()) {
182       FieldAttributes fieldAttributes = new FieldAttributes(field);
183       for (ExclusionStrategy exclusionStrategy : list) {
184         if (exclusionStrategy.shouldSkipField(fieldAttributes)) {
185           return true;
186         }
187       }
188     }
189
190     return false;
191   }
192
193   private boolean excludeClassChecks(Class<?> clazz) {
194       if (version != Excluder.IGNORE_VERSIONS && !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) {
195           return true;
196       }
197
198       if (!serializeInnerClasses && isInnerClass(clazz)) {
199           return true;
200       }
201
202       if (isAnonymousOrLocal(clazz)) {
203           return true;
204       }
205
206       return false;
207   }
208
209   public boolean excludeClass(Class<?> clazz, boolean serialize) {
210       return excludeClassChecks(clazz) ||
211               excludeClassInStrategy(clazz, serialize);
212   }
213
214   private boolean excludeClassInStrategy(Class<?> clazz, boolean serialize) {
215       List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
216       for (ExclusionStrategy exclusionStrategy : list) {
217           if (exclusionStrategy.shouldSkipClass(clazz)) {
218               return true;
219           }
220       }
221       return false;
222   }
223
224   private boolean isAnonymousOrLocal(Class<?> clazz) {
225     return !Enum.class.isAssignableFrom(clazz)
226         && (clazz.isAnonymousClass() || clazz.isLocalClass());
227   }
228
229   private boolean isInnerClass(Class<?> clazz) {
230     return clazz.isMemberClass() && !isStatic(clazz);
231   }
232
233   private boolean isStatic(Class<?> clazz) {
234     return (clazz.getModifiers() & Modifier.STATIC) != 0;
235   }
236
237   private boolean isValidVersion(Since since, Until until) {
238     return isValidSince(since) && isValidUntil(until);
239   }
240
241   private boolean isValidSince(Since annotation) {
242     if (annotation != null) {
243       double annotationVersion = annotation.value();
244       if (annotationVersion > version) {
245         return false;
246       }
247     }
248     return true;
249   }
250
251   private boolean isValidUntil(Until annotation) {
252     if (annotation != null) {
253       double annotationVersion = annotation.value();
254       if (annotationVersion <= version) {
255         return false;
256       }
257     }
258     return true;
259   }
260 }
261