-
Notifications
You must be signed in to change notification settings - Fork 73
Expand file tree
/
Copy pathMetaFieldUtil.java
More file actions
290 lines (256 loc) · 13.7 KB
/
Copy pathMetaFieldUtil.java
File metadata and controls
290 lines (256 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
package com.cloudhopper.commons.util;
/*
* #%L
* ch-commons-util
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudhopper.commons.util.annotation.MetaField;
/**
* Utility class for generating an array of MetaFieldInfo objects at runtime by
* reflecting a class and searching for the MetaField annotation.
* <br>
* NOTE: This automatically handles unwrapping certain types such as an AtomicReference
* where the actual values you'd want to validate is the one contained within.
*
* @author joelauer (twitter: @jjlauer or <a href="http://twitter.com/jjlauer" target=window>http://twitter.com/jjlauer</a>)
*/
public class MetaFieldUtil {
private static Logger logger = LoggerFactory.getLogger(MetaFieldUtil.class);
/**
* Creates a string for an object based on the MetaField annotations.
* @param obj The object to search
* @return A string representing the current values of any MetaFields
*/
public static String toMetaFieldInfoString(Object obj) {
StringBuffer buf = new StringBuffer(100);
MetaFieldInfo[] fields = toMetaFieldInfoArray(obj, null, true);
for (int i = 0; i < fields.length; i++) {
MetaFieldInfo field = fields[i];
buf.append(field.name);
buf.append("=");
// if the field's actual value is non-null and of type String
if (field.actualValue != null && field.actualValue.getClass().equals(String.class)) {
buf.append('"');
buf.append(field.value);
buf.append('"');
} else {
buf.append(field.value);
}
// if not last element, delimit with a comma
if (i+1 < fields.length)
buf.append(",");
}
return buf.toString();
}
/**
* Returns a new MetaFieldInfo array of all Fields annotated with MetaField
* in the runtime object type. If the object is null, this method will
* return null. Will use the default "null" string representation of
* null values. NOTE: This method recursively searches the object.
* @param obj The runtime instance of the object to search. It must be an
* instance of the type. If its a subclass of the type, this method
* will only return MetaFields of the type passed in.
* @return The MetaFieldInfo array
*/
public static MetaFieldInfo[] toMetaFieldInfoArray(Object obj) {
return toMetaFieldInfoArray(obj, null);
}
/**
* Returns a new MetaFieldInfo array of all Fields annotated with MetaField
* in the runtime object type. If the object is null, this method will
* return null. NOTE: This method recursively searches the object.
* @param obj The runtime instance of the object to search. It must be an
* instance of the type. If its a subclass of the type, this method
* will only return MetaFields of the type passed in.
* @param stringForNullValues If a field is null, this is the string to swap
* in as the String value vs. "null" showing up.
* @return The MetaFieldInfo array
*/
public static MetaFieldInfo[] toMetaFieldInfoArray(Object obj, String stringForNullValues) {
return toMetaFieldInfoArray(obj, stringForNullValues, false);
}
/**
* Returns a new MetaFieldInfo array of all Fields annotated with MetaField
* in the runtime object type. If the object is null, this method will
* return null. NOTE: This method recursively searches the object.
* @param obj The runtime instance of the object to search. It must be an
* instance of the type. If its a subclass of the type, this method
* will only return MetaFields of the type passed in.
* @param stringForNullValues If a field is null, this is the string to swap
* in as the String value vs. "null" showing up.
* @param ignoreAnnotatedName Whether to ignore the name of the MetaField
* and always return the field name instead. If false, this method will
* use the annotated name if it exists, otherwise it'll use the field name.
* @return The MetaFieldInfo array
*/
public static MetaFieldInfo[] toMetaFieldInfoArray(Object obj, String stringForNullValues, boolean ignoreAnnotatedName) {
return toMetaFieldInfoArray((obj != null ? obj.getClass() : null), obj, stringForNullValues, ignoreAnnotatedName, true);
}
/**
* Returns a new MetaFieldInfo array of all Fields annotated with MetaField
* in the object type. The object must be an instance of the type, otherwise
* this method may throw a runtime exception. Optionally, this method can
* recursively search the object to provide a deep view of the entire class.
* @param type The class type of the object
* @param obj The runtime instance of the object to search. It must be an
* instance of the type. If its a subclass of the type, this method
* will only return MetaFields of the type passed in.
* @param stringForNullValues If a field is null, this is the string to swap
* in as the String value vs. "null" showing up.
* @param ignoreAnnotatedName Whether to ignore the name of the MetaField
* and always return the field name instead. If false, this method will
* use the annotated name if it exists, otherwise it'll use the field name.
* @param recursive If true, this method will recursively search any fields
* to see if they also contain MetaField annotations. If they do, this
* method will include those in its returned array.
* @return The MetaFieldInfo array
*/
public static MetaFieldInfo[] toMetaFieldInfoArray(Class type, Object obj, String stringForNullValues, boolean ignoreAnnotatedName, boolean recursive) {
return internalToMetaFieldInfoArray(type, obj, null, null, stringForNullValues, ignoreAnnotatedName, recursive);
}
/**
* Optionally, recursively returns a new MetaFieldInfo array of all Fields
* annotated with MetaField in the object. If recursive is true, this will
* also search any fields in each class and add them in their order as well.
* @param type The class type of the obj (in case obj is null)
* @param obj The object to search. If this is null, will default values for each field to null.
* @param appendFieldName Will append this value to the name of the field (really for subfields)
* @param stringForNullValues If a field is null, this is the string to swap
* in as the String value vs. "null" showing up.
* @return The new MetaFieldInfo array
*/
protected static MetaFieldInfo[] internalToMetaFieldInfoArray(Class type, Object obj, String preFieldName,
String postFieldName, String stringForNullValues, boolean ignoreAnnotatedName, boolean recursive) {
// get the class hierarchy
Class[] hierarchy = ClassUtil.getClassHierarchy(type);
// if the class hierarchy is 0 (there are definitely no meta fields to process
if (hierarchy == null || hierarchy.length == 0)
return null;
// create the array we'll return
ArrayList<MetaFieldInfo> infos = new ArrayList<MetaFieldInfo>();
// make sure appendFieldName is always an empty String
if (preFieldName == null)
preFieldName = "";
if (postFieldName == null)
postFieldName = "";
// keep searching up until we reach an Object class type
for (Class classType : hierarchy) {
// begin search for each field (variable)
Field[] fields = classType.getDeclaredFields();
Arrays.sort(fields, new Comparator<Field>() {
public int compare(Field a, Field b) {
return a.getName().compareTo(b.getName());
}
});
for (Field f : fields) {
// is this field marked as a MetaField?
if (f.isAnnotationPresent(MetaField.class)) {
// get the annotation
MetaField metaField = f.getAnnotation(MetaField.class);
// create the new counter info object we'll store
// now, this class may actually still represent a class we want to recursively search
// we'll create the new info object for now, but this may change
MetaFieldInfo info = new MetaFieldInfo();
// save the field name
info.fieldName = f.getName();
// save the field class
info.fieldClass = f.getType();
// save the level
info.level = metaField.level();
// is there an annotated name? if not we'll default to the field name
info.name = preFieldName + (!metaField.name().equals("") && !ignoreAnnotatedName ? metaField.name() : f.getName()) + postFieldName;
// default the recursive pre field names
String recursivePreFieldName = preFieldName + f.getName() + ".";
String recursivePostFieldName = "";
// if this had an actual name, not using the field name, slightly diff format
if (!metaField.name().equals("") && !ignoreAnnotatedName) {
// only add [ and ] if this is not the first
if (!preFieldName.equals("")) {
recursivePreFieldName = preFieldName + metaField.name() + "][";
recursivePostFieldName = "]";
} else {
recursivePreFieldName = metaField.name() + "[";
recursivePostFieldName = "]";
}
}
// required to read private variables
f.setAccessible(true);
// read the actual value if the obj isn't null
if (obj != null) {
// get the actual value
try {
info.actualValue = f.get(obj);
} catch (Exception e) {
logger.error("impossible case if run outside of an applet");
}
//
// handle "unwrapping" objects -- AtomicReference
//
if (info.actualValue != null && f.getType().equals(AtomicReference.class)) {
//logger.debug("AtomicReference found, going to unwrap it");
AtomicReference ref = (AtomicReference)info.actualValue;
// get the actual underlying value
info.actualValue = ref.get();
// if not null, we can get its type too
if (info.actualValue != null) {
info.fieldClass = info.actualValue.getClass();
}
}
} else {
info.actualValue = null;
}
// get a proper string value of this counter
if (info.actualValue == null) {
info.value = stringForNullValues;
} else {
info.value = info.actualValue.toString();
}
// add our annotated description
info.description = metaField.description();
// if this is recursive, we need to check of the subvalues are actually wanted instead...
// if the actual value is null though, we know the sub-values are null...
if (recursive) {
// see if there are any MetaFields in the sub-object
MetaFieldInfo[] subInfos = internalToMetaFieldInfoArray(info.fieldClass, info.actualValue, recursivePreFieldName, recursivePostFieldName, stringForNullValues, ignoreAnnotatedName, recursive);
// was anything returned
if (subInfos == null || subInfos.length == 0) {
infos.add(info);
} else {
// otherwise, add everything
// if there were any, also add these
for (MetaFieldInfo subInfo : subInfos) {
infos.add(subInfo);
}
}
} else {
// add this to our array
infos.add(info);
}
}
}
}
// return our array
return infos.toArray(new MetaFieldInfo[0]);
}
}