1
2
3
4
5
6
7
8
9
10
11
12
13
14 package com.addthis.codec.binary;
15
16 import javax.annotation.Nullable;
17
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.io.PrintWriter;
21 import java.io.StringWriter;
22
23 import java.lang.reflect.Array;
24 import java.lang.reflect.Modifier;
25
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.concurrent.atomic.AtomicInteger;
33 import java.util.concurrent.atomic.AtomicLong;
34
35 import com.addthis.basis.util.Bytes;
36
37 import com.addthis.codec.Codec;
38 import com.addthis.codec.codables.Codable;
39 import com.addthis.codec.codables.ConcurrentCodable;
40 import com.addthis.codec.codables.SuperCodable;
41 import com.addthis.codec.reflection.CodableClassInfo;
42 import com.addthis.codec.reflection.CodableFieldInfo;
43 import com.addthis.codec.reflection.Fields;
44
45 import com.google.common.base.Strings;
46
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50
51
52
53
54 public final class CodecBin2 implements Codec {
55
56 private static final Logger log = LoggerFactory.getLogger(CodecBin2.class);
57
58 public static final CodecBin2 INSTANCE = new CodecBin2(false);
59 public static final int CODEC_VERSION = 2;
60
61 private final boolean charstring;
62
63 private CodecBin2(boolean cs) { this.charstring = cs; }
64
65 @Override
66 public byte[] encode(Object obj) throws Exception {
67 return encodeBytes(obj);
68 }
69
70 @Override
71 public Object decode(Class type, byte[] data) throws Exception {
72 return decode(type.newInstance(), data);
73 }
74
75 @Override
76 public Object decode(Object shell, byte[] data) throws Exception {
77 return decodeBytes(shell, data);
78 }
79
80 @Override
81 public boolean storesNull(byte[] data) {
82 return (data.length == 5) && (data[4] == 0);
83 }
84
85 public static byte[] encodeBytes(Object object) throws Exception {
86 BufferOut buf = new BufferOut();
87 Bytes.writeInt(CODEC_VERSION, buf.out());
88 INSTANCE.encodeObject(object, buf);
89 return buf.out.toByteArray();
90 }
91
92 @Nullable @SuppressWarnings("unchecked")
93 public static Object decodeBytes(Object object, byte[] data) throws Exception {
94 BufferIn buf = new BufferIn(data);
95 int ver = Bytes.readInt(buf.in);
96 require(ver == CODEC_VERSION, "version mismatch " + ver + " != " + CODEC_VERSION);
97 return INSTANCE.decodeObject(Fields.getClassFieldMap(object.getClass()), object, buf);
98 }
99
100 private void encodeObject(Object object, BufferOut buf)
101 throws Exception {
102 log.trace("encodeObject: {} {}", object, buf);
103 if (object == null) {
104 buf.out.write(0);
105 return;
106 }
107 boolean lock = object instanceof ConcurrentCodable;
108 if (lock) {
109 ((ConcurrentCodable) object).encodeLock();
110 }
111 try {
112 if (object instanceof SuperCodable) {
113 ((SuperCodable) object).preEncode();
114 }
115 Class objectClass = object.getClass();
116 CodableClassInfo classInfo = Fields.getClassFieldMap(objectClass);
117 if (objectClass.isArray()) {
118 encodeArray(object, objectClass, buf);
119 } else if ((classInfo.size() == 0) && !(object instanceof Codable)) {
120 encodeNative(object, buf);
121 } else {
122 buf.out.write(1);
123 writeStringHelper(classInfo.getClassName(object), buf.out());
124 for (CodableFieldInfo field : classInfo.values()) {
125 encodeField(field.get(object), field, buf);
126 }
127 }
128 } finally {
129 if (lock) {
130 ((ConcurrentCodable) object).encodeUnlock();
131 }
132 }
133 }
134
135 @Nullable private Object decodeObject(Class<?> type, BufferIn buf) throws Exception {
136 log.trace("decodeObject: {} {}", type, buf);
137 if (Fields.isNative(type)) {
138 return decodeNative(type, buf);
139 } else {
140 CodableClassInfo classInfo = Fields.getClassFieldMap(type);
141 return decodeObject(classInfo, null, buf);
142 }
143 }
144
145 @Nullable private Object decodeObject(CodableClassInfo classInfo, @Nullable Object object, BufferIn buf) throws Exception {
146 int ck = buf.in.read();
147 if (ck == 0) {
148 return null;
149 }
150 Class<?> type = classInfo.getBaseClass();
151 log.trace("decodeObject: {} {} {}", classInfo, object, buf);
152 String stype = readStringHelper(buf.in);
153 if (!Strings.isNullOrEmpty(stype)) {
154 Class<?> atype = classInfo.getClass(stype);
155 if (type != atype) {
156 classInfo = Fields.getClassFieldMap(atype);
157 type = atype;
158 }
159 }
160 if (object == null) {
161 object = type.newInstance();
162 }
163 for (CodableFieldInfo field : classInfo.values()) {
164 field.set(object, decodeField(field, buf));
165 }
166 if (object instanceof SuperCodable) {
167 ((SuperCodable) object).postDecode();
168 }
169 return object;
170 }
171
172 private void encodeArray(Object value, Class<?> type, BufferOut buf) throws Exception {
173 int len = Array.getLength(value);
174 log.trace("encodeArray: {} {} {} len={}", value, type, buf, len);
175 Bytes.writeLength(len, buf.out());
176 if ((type == byte.class) || (type == Byte.class)) {
177 buf.out.write((byte[]) value);
178 } else if ((type == int.class) || (type == Integer.class)) {
179 int[] val = (int[]) value;
180 for (int i = 0; i < len; i++) {
181 Bytes.writeInt(val[i], buf.out());
182 }
183 } else if ((type == long.class) || (type == Long.class)) {
184 long[] val = (long[]) value;
185 for (int i = 0; i < len; i++) {
186 Bytes.writeLong(val[i], buf.out());
187 }
188 } else if (type.isEnum()) {
189 for (int i = 0; i < len; i++) {
190 encodeNative(Array.get(value, i).toString(), buf);
191 }
192 } else {
193 for (int i = 0; i < len; i++) {
194 encodeObject(Array.get(value, i), buf);
195 }
196 }
197 }
198
199 @Nullable private Object decodeArray(Class<?> type, BufferIn buf) throws Exception {
200 log.trace("decodeArray: {} {}", type, buf);
201 int len = (int) Bytes.readLength(buf.in);
202 Object value = null;
203 if (len > 0) {
204 value = Array.newInstance(type, len);
205 if ((type == byte.class) || (type == Byte.class)) {
206 buf.in.read((byte[]) value);
207 } else if ((type == int.class) || (type == Integer.class)) {
208 int[] val = (int[]) value;
209 for (int i = 0; i < len; i++) {
210 val[i] = Bytes.readInt(buf.in);
211 }
212 value = val;
213 } else if ((type == long.class) || (type == Long.class)) {
214 long[] val = (long[]) value;
215 for (int i = 0; i < len; i++) {
216 val[i] = Bytes.readLong(buf.in);
217 }
218 value = val;
219 } else if (type.isEnum()) {
220 for (int i = 0; i < len; i++) {
221 Array.set(value, i, decodeEnum((Class<Enum>) type, buf));
222 }
223 } else {
224 for (int i = 0; i < len; i++) {
225 Array.set(value, i, decodeObject(type, buf));
226 }
227 }
228 }
229 return value;
230 }
231
232 private void encodeField(Object value, CodableFieldInfo field, BufferOut buf) throws Exception {
233 log.trace("encodeField: {} {} {}", value, field, buf);
234 if (value != null) {
235 try {
236 buf.out.write(1);
237 if (field.isArray()) {
238 encodeArray(value, field.getTypeOrComponentType(), buf);
239 } else if (field.isNative()) {
240 encodeNative(value, buf);
241 } else if (field.isMap()) {
242 Map<?, ?> map = (Map<?, ?>) value;
243 Bytes.writeLength(map.size(), buf.out());
244 for (Entry<?, ?> entry : map.entrySet()) {
245 Object key = entry.getKey();
246 encodeObject(key, buf);
247 encodeObject(entry.getValue(), buf);
248 }
249 } else if (field.isCollection()) {
250 Collection<?> coll = (Collection<?>) value;
251 Bytes.writeLength(coll.size(), buf.out());
252 for (Object aColl : coll) {
253 encodeObject(aColl, buf);
254 }
255 } else if (field.isCodable()) {
256 encodeObject(value, buf);
257 } else if (field.isEnum()) {
258 encodeNative(value.toString(), buf);
259 } else {
260 log.warn("[encodeField] unhandled field : {} {}", value, field);
261 }
262 } catch (Exception ex) {
263 log.warn("failed encoding {} class {} type {}", value, value.getClass(), field, ex);
264 StringWriter sw = new StringWriter();
265 PrintWriter pw = new PrintWriter(sw);
266 ex.printStackTrace(pw);
267 log.warn(sw.toString());
268 throw ex;
269 }
270 } else {
271 buf.out.write(0);
272 }
273 }
274
275 private static boolean isNotConcrete(Class<?> type) {
276 int mod = type.getModifiers();
277 return Modifier.isAbstract(mod) || Modifier.isInterface(mod);
278 }
279
280 @SuppressWarnings("unchecked")
281 private static Map<Object, Object> newMap(Class<?> type) throws InstantiationException, IllegalAccessException {
282 return isNotConcrete(type) ? new HashMap<>() : (Map<Object, Object>) type.newInstance();
283 }
284
285 @SuppressWarnings("unchecked")
286 private static Collection<Object> newCollection(Class<?> type, int size) throws InstantiationException, IllegalAccessException {
287 return isNotConcrete(type) ? new ArrayList<>(size) : (Collection<Object>) type.newInstance();
288 }
289
290 @Nullable private Object decodeField(CodableFieldInfo field, BufferIn buf) throws Exception {
291 log.trace("decodeField: {} {}", field, buf);
292 int ck = buf.in.read();
293 if (ck == 0) {
294 return null;
295 }
296 Class<?> type = field.getTypeOrComponentType();
297 if (field.isArray()) {
298 return decodeArray(type, buf);
299 } else if (field.isMap()) {
300 Map<Object, Object> map = (Map<Object, Object>) newMap(type);
301 int elements = (int) Bytes.readLength(buf.in);
302 if (elements == 0) {
303 return map;
304 }
305
306 Class<?> kc = field.getMapKeyClass();
307 Class<?> vc = field.getMapValueClass();
308 boolean ka = field.isMapKeyArray();
309 boolean va = field.isMapValueArray();
310 for (int i = 0; i < elements; i++) {
311 if (ka) {
312 if (va) {
313 map.put(decodeArray(kc, buf), decodeArray(vc, buf));
314 } else {
315 map.put(decodeArray(kc, buf), decodeObject(vc, buf));
316 }
317 } else {
318 if (va) {
319 map.put(decodeObject(kc, buf), decodeArray(vc, buf));
320 } else {
321 map.put(decodeObject(kc, buf), decodeObject(vc, buf));
322 }
323 }
324 }
325 return map;
326 } else if (field.isCollection()) {
327 int elements = (int) Bytes.readLength(buf.in);
328 Collection<Object> coll = (Collection<Object>) newCollection(type, elements);
329 if (elements == 0) {
330 return coll;
331 }
332 Class<?> vc = field.getCollectionClass();
333 boolean va = field.isCollectionArray();
334 for (int i = 0; i < elements; i++) {
335 coll.add(va ? decodeArray(vc, buf) : decodeObject(vc, buf));
336 }
337 return coll;
338 } else if (field.isCodable()) {
339 return decodeObject(type, buf);
340 } else if (field.isEnum()) {
341 return decodeEnum((Class<Enum>) type, buf);
342 } else if (field.isNative()) {
343 return decodeNative(type, buf);
344 } else {
345 log.warn("unhandled decode {}", field);
346 return null;
347 }
348 }
349
350 private void encodeNative(Object value, BufferOut buf) throws Exception {
351 log.trace("encodeNative: {} {}", value, buf);
352 Class<?> type = value.getClass();
353 if (type == String.class) {
354 writeStringHelper(value.toString(), buf.out());
355 } else if ((type == Integer.class) || (type == int.class)) {
356 Bytes.writeInt((Integer) value, buf.out());
357 } else if ((type == Long.class) || (type == long.class)) {
358 Bytes.writeLong((Long) value, buf.out());
359 } else if ((type == Short.class) || (type == short.class)) {
360 Bytes.writeShort((Short) value, buf.out());
361 } else if ((type == Boolean.class) || (type == boolean.class)) {
362 buf.out.write((Boolean) value ? 1 : 0);
363 } else if ((type == Float.class) || (type == float.class)) {
364 Bytes.writeInt(Float.floatToIntBits(((Float) value)), buf.out());
365 } else if ((type == Double.class) || (type == double.class)) {
366 Bytes.writeLong(Double.doubleToLongBits(((Double) value)), buf.out());
367 } else if (type == AtomicLong.class) {
368 Bytes.writeLong(((AtomicLong) value).get(), buf.out());
369 } else if (type == AtomicInteger.class) {
370 Bytes.writeInt(((AtomicInteger) value).get(), buf.out());
371 } else if (type == AtomicBoolean.class) {
372 buf.out.write(((AtomicBoolean) value).get() ? 1 : 0);
373 } else {
374 log.warn("skip native encode for {} / {}", value, value.getClass());
375 }
376 }
377
378 private Object decodeEnum(Class<Enum> type, BufferIn buf) throws Exception {
379 String val = readStringHelper(buf.in);
380 return Enum.valueOf(type, val);
381 }
382
383 @Nullable private Object decodeNative(Class<?> type, BufferIn buf) throws Exception {
384 Object result = null;
385 if (type == String.class) {
386 result = readStringHelper(buf.in);
387 } else if ((type == Integer.class) || (type == int.class)) {
388 result = Bytes.readInt(buf.in);
389 } else if ((type == Long.class) || (type == long.class)) {
390 result = Bytes.readLong(buf.in);
391 } else if ((type == Short.class) || (type == short.class)) {
392 result = Bytes.readShort(buf.in);
393 } else if ((type == Boolean.class) || (type == boolean.class)) {
394 result = buf.in.read() != 0 ? true : false;
395 } else if ((type == Double.class) || (type == double.class)) {
396 result = Double.longBitsToDouble(Bytes.readLong(buf.in));
397 } else if ((type == Float.class) || (type == float.class)) {
398 result = Float.intBitsToFloat(Bytes.readInt(buf.in));
399 } else if (type == AtomicLong.class) {
400 result = new AtomicLong(Bytes.readLong(buf.in));
401 } else if (type == AtomicInteger.class) {
402 result = new AtomicInteger(Bytes.readInt(buf.in));
403 } else if (type == AtomicBoolean.class) {
404 result = buf.in.read() != 0 ? new AtomicBoolean(true) : new AtomicBoolean(false);
405 } else {
406 log.warn("unhandled native decode {}", type);
407 }
408 return result;
409 }
410
411 private static void require(boolean bool, String msg) throws Exception {
412 if (!bool) {
413 throw new Exception(msg);
414 }
415 }
416
417 @Nullable private String readStringHelper(InputStream in) throws Exception {
418 if (charstring) {
419 return Bytes.readCharString(in);
420 } else {
421 return Bytes.readString(in);
422 }
423 }
424
425 private void writeStringHelper(String str, OutputStream out) throws Exception {
426 if (charstring) {
427 Bytes.writeCharString(str, out);
428 } else {
429 Bytes.writeString(str, out);
430 }
431 }
432 }