1
2
3
4
5
6
7
8
9
10
11
12
13
14 package com.addthis.codec.reflection;
15
16 import javax.annotation.Nonnull;
17 import javax.annotation.Nullable;
18
19 import java.lang.reflect.Field;
20 import java.lang.reflect.GenericArrayType;
21 import java.lang.reflect.Type;
22
23 import java.util.Collection;
24 import java.util.Map;
25
26 import com.addthis.codec.annotations.FieldConfig;
27 import com.addthis.codec.codables.Codable;
28
29 import com.google.common.annotations.Beta;
30
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34
35
36
37 @Beta
38 public final class CodableFieldInfo {
39
40 private static final Logger log = LoggerFactory.getLogger(CodableFieldInfo.class);
41
42 public static final int ARRAY = 1 << 0;
43 public static final int CODABLE = 1 << 1;
44 public static final int COLLECTION = 1 << 2;
45 public static final int GENERIC = 1 << 3;
46 public static final int NATIVE = 1 << 4;
47 public static final int MAP = 1 << 5;
48 public static final int NUMBER = 1 << 6;
49 public static final int REQUIRED = 1 << 7;
50 public static final int READONLY = 1 << 8;
51 public static final int WRITEONLY = 1 << 9;
52 public static final int ENUM = 1 << 10;
53
54 @Nonnull private final Field field;
55 @Nonnull private final Class<?> typeOrComponentType;
56 private final int bits;
57
58 @Nullable private final FieldConfig fieldConfig;
59 @Nullable private final Type[] genTypes;
60 @Nullable private final boolean[] genArray;
61
62 public CodableFieldInfo(@Nonnull Field field) {
63 this.field = field;
64 field.setAccessible(true);
65 fieldConfig = field.getAnnotation(FieldConfig.class);
66
67 Class<?> type = field.getType();
68 boolean array = type.isArray();
69 if (array) {
70 typeOrComponentType = type.getComponentType();
71 this.bits = cacheFlags(CodableFieldInfo.ARRAY);
72 } else {
73 typeOrComponentType = type;
74 this.bits = cacheFlags(0);
75 }
76
77 if (!Fields.isNative(typeOrComponentType)) {
78 genTypes = Fields.collectTypes(typeOrComponentType, field.getGenericType());
79 } else {
80 genTypes = null;
81 }
82 if (genTypes == null) {
83 genArray = null;
84 } else {
85 genArray = new boolean[genTypes.length];
86 mutateGenericTypes(genTypes, genArray);
87 }
88 }
89
90 private int cacheFlags(int externalBits) {
91 int partialBits = externalBits;
92 if (Codable.class.isAssignableFrom(typeOrComponentType)) {
93 partialBits |= CodableFieldInfo.CODABLE;
94 }
95 if (Collection.class.isAssignableFrom(typeOrComponentType)) {
96 partialBits |= CodableFieldInfo.COLLECTION;
97 }
98 if (Map.class.isAssignableFrom(typeOrComponentType)) {
99 partialBits |= CodableFieldInfo.MAP;
100 }
101 if (typeOrComponentType.isEnum()) {
102 partialBits |= CodableFieldInfo.ENUM;
103 }
104 if (Number.class.isAssignableFrom(typeOrComponentType)) {
105 partialBits |= CodableFieldInfo.NUMBER;
106 }
107 if (Fields.isNative(typeOrComponentType)) {
108 partialBits |= CodableFieldInfo.NATIVE;
109 }
110 if (fieldConfig != null) {
111 if (fieldConfig.readonly()) {
112 partialBits |= CodableFieldInfo.READONLY;
113 }
114 if (fieldConfig.writeonly()) {
115 partialBits |= CodableFieldInfo.WRITEONLY;
116 }
117 if (fieldConfig.codable()) {
118 partialBits |= CodableFieldInfo.CODABLE;
119 }
120 if (fieldConfig.required()) {
121 partialBits |= CodableFieldInfo.REQUIRED;
122 }
123 }
124 return partialBits;
125 }
126
127 @Nonnull public Field getField() {
128 return field;
129 }
130
131 public String getName() {
132 return field.getName();
133 }
134
135 @Nonnull public Class<?> getTypeOrComponentType() {
136 return typeOrComponentType;
137 }
138
139 @Nullable public Type[] getGenericTypes() {
140 return genTypes;
141 }
142
143
144 private void mutateGenericTypes(@Nonnull final Type[] collectedTypes,
145 @Nonnull final boolean[] genericFlags) {
146 for (int i = 0; i < collectedTypes.length; i++) {
147 Type currentType = collectedTypes[i];
148 if (currentType instanceof GenericArrayType) {
149 genericFlags[i] = true;
150 collectedTypes[i] = ((GenericArrayType) currentType).getGenericComponentType();
151 } else if ((currentType instanceof Class) && ((Class) currentType).isArray()) {
152 genericFlags[i] = true;
153 collectedTypes[i] = ((Class) currentType).getComponentType();
154 } else {
155 genericFlags[i] = false;
156 }
157 }
158 }
159
160 public Object newInstance() throws Exception {
161 return typeOrComponentType.newInstance();
162 }
163
164 @Nullable public Class<?> getCollectionClass() {
165 return ((genTypes != null) && (genTypes.length == 1)) ? (Class<?>) genTypes[0] : null;
166 }
167
168 @Nullable public Class<?> getMapKeyClass() {
169 return ((genTypes != null) && (genTypes.length == 2)) ? (Class<?>) genTypes[0] : null;
170 }
171
172 @Nullable public Class<?> getMapValueClass() {
173 return ((genTypes != null) && (genTypes.length == 2)) ? (Class<?>) genTypes[1] : null;
174 }
175
176 public boolean isCollectionArray() {
177 return ((genArray != null) && (genArray.length == 1)) ? genArray[0] : false;
178 }
179
180 public boolean isMapKeyArray() {
181 return ((genArray != null) && (genArray.length == 2)) ? genArray[0] : false;
182 }
183
184 public boolean isMapValueArray() {
185 return ((genArray != null) && (genArray.length == 2)) ? genArray[1] : false;
186 }
187
188 public Object get(Object src) {
189 try {
190 return field.get(src);
191 } catch (IllegalAccessException e) {
192 throw new RuntimeException(e);
193 }
194 }
195
196
197
198
199
200
201 public void setStrict(@Nonnull Object dst, @Nullable Object value) throws IllegalAccessException {
202 if (value == null) {
203 if (isRequired()) {
204 throw new RequiredFieldException("missing required field '" +
205 this.getName() + "' for " + dst, getName());
206 }
207 return;
208 }
209 field.set(dst, value);
210 }
211
212 public void set(@Nonnull Object dst, @Nullable Object value) {
213 if (value == null) {
214 if (isRequired() && (get(dst) == null)) {
215 throw new RequiredFieldException("missing required field '" +
216 this.getName() + "' for " + dst, getName());
217 }
218 return;
219 }
220 try {
221 field.set(dst, value);
222 } catch (RuntimeException ex) {
223 throw ex;
224 } catch (Exception ex) {
225 log.warn("error setting ({})({}) on ({}) in {}", value, value.getClass(), dst, toString());
226 throw new RuntimeException(ex);
227 }
228 }
229
230 public boolean isArray() {
231 return (bits & ARRAY) == ARRAY;
232 }
233
234 public boolean isCodable() {
235 return (bits & CODABLE) == CODABLE;
236 }
237
238 public boolean isCollection() {
239 return (bits & COLLECTION) == COLLECTION;
240 }
241
242 public boolean isGeneric() {
243 return (bits & GENERIC) == GENERIC;
244 }
245
246 public boolean isMap() {
247 return (bits & MAP) == MAP;
248 }
249
250 public boolean isEnum() {
251 return (bits & ENUM) == ENUM;
252 }
253
254 public boolean isNative() {
255 return (bits & NATIVE) == NATIVE;
256 }
257
258 public boolean isRequired() {
259 return (bits & REQUIRED) == REQUIRED;
260 }
261
262 public boolean isReadOnly() {
263 return (bits & READONLY) == READONLY;
264 }
265
266 public boolean isWriteOnly() {
267 return (bits & WRITEONLY) == WRITEONLY;
268 }
269
270 public String toString() {
271 return "[" + getName() + "," + typeOrComponentType + (isArray() ? "[]," : ",") + Integer.toString(bits, 2) + "]";
272 }
273 }