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.Modifier;
21
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.SortedMap;
26 import java.util.TreeMap;
27
28 import com.addthis.codec.annotations.FieldConfig;
29 import com.addthis.codec.annotations.Pluggable;
30 import com.addthis.codec.plugins.PluginMap;
31 import com.addthis.codec.plugins.PluginRegistry;
32
33 import com.google.common.annotations.Beta;
34 import com.google.common.collect.ImmutableSortedMap;
35
36 import com.typesafe.config.Config;
37 import com.typesafe.config.ConfigFactory;
38 import com.typesafe.config.ConfigObject;
39 import com.typesafe.config.ConfigValue;
40
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 @Beta
45 public class CodableClassInfo {
46 private static final Logger log = LoggerFactory.getLogger(CodableClassInfo.class);
47
48 @Nonnull private final Class<?> baseClass;
49 @Nonnull private final PluginMap pluginMap;
50 @Nonnull private final Config fieldDefaults;
51 @Nonnull private final ImmutableSortedMap<String, CodableFieldInfo> classData;
52
53 public CodableClassInfo(@Nonnull Class<?> clazz) {
54 this(clazz, ConfigFactory.load(), PluginRegistry.defaultRegistry());
55 }
56
57 public CodableClassInfo(@Nonnull Class<?> clazz,
58 @Nonnull Config globalDefaults,
59 @Nonnull PluginRegistry pluginRegistry) {
60
61
62 if (Fields.isNative(clazz) || clazz.isArray()) {
63 classData = ImmutableSortedMap.of();
64 baseClass = clazz;
65 pluginMap = PluginMap.EMPTY;
66 fieldDefaults = ConfigFactory.empty();
67 return;
68 }
69
70
71 Class<?> findBaseClass = clazz;
72 PluginMap findPluginMap = PluginMap.EMPTY;
73
74 Class<?> ptr = clazz;
75 while (ptr != null) {
76 if (pluginRegistry.byClass().containsKey(ptr)) {
77 findPluginMap = pluginRegistry.byClass().get(ptr);
78 findBaseClass = ptr;
79 break;
80 }
81 Pluggable pluggable = ptr.getAnnotation(Pluggable.class);
82 if (pluggable != null) {
83 String category = pluggable.value();
84 findPluginMap = pluginRegistry.asMap().get(category);
85 findBaseClass = ptr;
86 if (findPluginMap == null) {
87 log.warn("missing plugin map for {}, reached from {}", ptr, clazz);
88 findPluginMap = PluginMap.EMPTY;
89 }
90 break;
91 }
92 ptr = ptr.getSuperclass();
93 }
94 pluginMap = findPluginMap;
95 baseClass = findBaseClass;
96
97
98 Map<String, Field> fields = new HashMap<>();
99
100 ConfigObject buildDefaults = ConfigFactory.empty().root();
101
102 Class<?> ptrForFields = clazz;
103 while (ptrForFields != null) {
104 String canonicalClassName = ptrForFields.getCanonicalName();
105 ConfigObject classDefaults;
106 if ((canonicalClassName != null) && globalDefaults.hasPath(canonicalClassName)) {
107 classDefaults = globalDefaults.getObject(canonicalClassName);
108 } else {
109 classDefaults = ConfigFactory.empty().root();
110 }
111 for (Field field : ptrForFields.getDeclaredFields()) {
112 if (fields.get(field.getName()) == null) {
113 fields.put(field.getName(), field);
114 } else {
115 classDefaults = classDefaults.withoutKey(field.getName());
116 log.debug("({}) ignoring field in parent class ({}) with duplicate name ({})",
117 clazz, ptrForFields, field.getName());
118 }
119 }
120 for (Map.Entry<String, ConfigValue> pair : classDefaults.entrySet()) {
121 if (!buildDefaults.containsKey(pair.getKey())) {
122 buildDefaults = buildDefaults.withValue(pair.getKey(), pair.getValue());
123 }
124 }
125 ptrForFields = ptrForFields.getSuperclass();
126 }
127 fieldDefaults = buildDefaults.toConfig();
128
129
130 Map<String, CodableFieldInfo> buildClassData = buildFieldInfoMap(fields.values());
131 classData = ImmutableSortedMap.<String, CodableFieldInfo>naturalOrder()
132 .putAll(buildClassData).build();
133 }
134
135 @Nonnull public PluginMap getPluginMap() {
136 return pluginMap;
137 }
138
139 @Nonnull public Config getFieldDefaults() {
140 return fieldDefaults;
141 }
142
143 @Nonnull public Class<?> getBaseClass() {
144 return baseClass;
145 }
146
147 @Nonnull public String getClassField() {
148 return pluginMap.classField();
149 }
150
151 @Nullable public Class<?> getArraySugar() {
152 return pluginMap.arraySugar();
153 }
154
155 @Nullable public Class<?> getDefaultSugar() {
156 return pluginMap.defaultSugar();
157 }
158
159 @Nullable public String getClassName(Object val) {
160 if (val.getClass() != baseClass) {
161 return pluginMap.getClassName(val.getClass());
162 } else {
163 return null;
164 }
165 }
166
167 @Nonnull public Class<?> getClass(String name) throws ClassNotFoundException {
168 return pluginMap.getClass(name);
169 }
170
171 public int size() {
172 return classData.size();
173 }
174
175 @Nonnull public Collection<CodableFieldInfo> values() {
176 return classData.values();
177 }
178
179
180 @Nonnull public Map<String, CodableFieldInfo> fields() {
181 return classData;
182 }
183
184
185
186
187
188 private static boolean isCodable(Field field) {
189 FieldConfig fieldConfigPolicy = field.getAnnotation(FieldConfig.class);
190 if (fieldConfigPolicy != null) {
191 return fieldConfigPolicy.codable();
192 }
193 int modifierBitSet = field.getModifiers();
194 return !Modifier.isFinal(modifierBitSet) && Modifier.isPublic(modifierBitSet);
195 }
196
197 @Nonnull private static Map<String, CodableFieldInfo> buildFieldInfoMap(Iterable<Field> fields) {
198 SortedMap<String, CodableFieldInfo> buildClassData = new TreeMap<>();
199 for (Field field : fields) {
200 if (isCodable(field)) {
201 CodableFieldInfo info = new CodableFieldInfo(field);
202 buildClassData.put(field.getName(), info);
203 }
204 }
205 return buildClassData;
206 }
207
208 }