
com.addthis.codec.Codec Maven / Gradle / Ivy
/*
* 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.
*/
package com.addthis.codec;
import javax.annotation.Nonnull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import com.addthis.maljson.LineNumberInfo;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
public abstract class Codec {
private static final Logger log = LoggerFactory.getLogger(Codec.class);
private static final ConcurrentHashMap, CodableClassInfo> fieldMaps = new ConcurrentHashMap, CodableClassInfo>();
public static enum TYPE {
BIN2(CodecBin2.class), BIN1(CodecBin1.class), JSON(CodecJSON.class), KV(CodecKV.class);
private TYPE(Class> type) {
this.type = type;
}
private Class> type;
public Codec getInstance() {
try {
return (Codec) type.newInstance();
} catch (Exception ex) {
return null;
}
}
}
/**
* all codecs must implement this
*/
public abstract byte[] encode(Object obj) throws Exception;
/**
* all codecs must implement this
*/
public abstract CodableStatistics statistics(Object obj) throws Exception;
/**
* all codecs must implement this
*/
public abstract T decode(T shell, byte data[]) throws Exception;
/**
* all codecs must implement this
*/
public abstract boolean storesNull(byte data[]);
/**
* helper to decode into a class instead of object shell
*/
public T decode(Class type, byte data[]) throws Exception {
return decode(type.newInstance(), data);
}
/**
* used to flag an object type as codable when it's a field in another
* object
*/
public static interface Codable {
}
/**
* used by classes that could be modified during encoding
*/
public static interface ConcurrentCodable extends Codable {
public boolean encodeLock();
public void encodeUnlock();
}
/**
* used to run code to 'transcode' values into codable primitives
*/
public static interface SuperCodable extends Codable {
public void postDecode();
public void preEncode();
}
/**
* for classes that want to handle their own direct serialization
*/
public static interface BytesCodable extends Codable {
public byte[] bytesEncode();
public void bytesDecode(byte b[]);
}
/**
* for classes that want to handle their own direct serialization and prefer to not
* use and/or allocate byte arrays
*/
public static interface ByteBufCodable extends Codable {
public void writeBytes(ByteBuf buf);
public void readBytes(ByteBuf buf);
}
@SuppressWarnings("serial")
public static class PolicyException extends Exception {
public PolicyException(String msg) {
super(msg);
}
}
@SuppressWarnings("serial")
public static class RequiredFieldException extends PolicyException {
private String field;
public RequiredFieldException(String msg, String field) {
super(msg);
this.field = field;
}
public String getField() {
return field;
}
}
@SuppressWarnings("serial")
public static class ValidationException extends PolicyException {
private String field;
public ValidationException(String msg, String field) {
super(msg);
this.field = field;
}
public String getField() {
return field;
}
}
@SuppressWarnings("serial")
public static class UnrecognizedFieldException extends CodecExceptionLineNumber {
private String field;
private Class clazz;
public UnrecognizedFieldException(String msg, @Nonnull LineNumberInfo info, String field, Class clazz) {
super(msg, info);
this.field = field;
this.clazz = clazz;
}
public String getField() {
return field;
}
public Class getErrorClass() {
return clazz;
}
}
public static interface Validator {
public boolean validate(CodableFieldInfo field, Object value);
}
public static class Truthinator implements Validator {
public boolean validate(CodableFieldInfo field, Object value) {
return true;
}
}
public static interface ClassMapFactory {
public ClassMap getClassMap();
}
/* A bi-directional map between Strings and Classes.
*/
public static class ClassMap {
private BiMap> map = HashBiMap.create();
public String getClassField() {
return "class";
}
public String getCategory() {
return null;
}
public ClassMap misnomerMap() {
return null;
}
public ClassMap add(Class> type) {
return add(type.getSimpleName(), type);
}
public java.util.Set getNames() {
return map.keySet();
}
public ClassMap add(String name, Class> type) {
if (map.put(name, type) != null) {
log.warn("warning: overriding class map for " + name + " with " + type);
}
return this;
}
public ClassMap remove(Class> type) {
map.inverse().remove(type);
return this;
}
public ClassMap remove(String name, Class> type) {
map.remove(name);
return this;
}
public boolean contains(String name) {
return map.containsKey(name);
}
public boolean contains(Class> type) {
return map.containsValue(type);
}
public String getClassName(Class> type) {
String alt = map.inverse().get(type);
return alt != null ? alt : type.getName();
}
public Class> getClass(String type) throws Exception {
Class> alt = map.get(type);
if (alt != null) {
return alt;
}
try {
Class theClass = Class.forName(type);
return (theClass);
} catch (ClassNotFoundException ex) {
throw classNameSuggestions(type, ex);
}
}
private Exception classNameSuggestions(String type, ClassNotFoundException ex) {
java.util.Set classNames = this.getNames();
String category = this.getCategory();
ClassMap misnomerMap = this.misnomerMap();
if (classNames.isEmpty()) {
return ex;
}
StringBuilder builder = new StringBuilder();
if (category != null) {
builder.append("Could not instantiate an instance of the ");
builder.append(category);
builder.append(" category that you have specified");
} else {
builder.append("Could not instantiate something you have specified");
}
builder.append(" with \"");
builder.append(type);
builder.append("\".");
if (misnomerMap != null && misnomerMap.getCategory() != null && misnomerMap.contains(type)) {
builder.append("\nIt looks like you tried to instantiate a ");
builder.append(misnomerMap.getCategory());
builder.append(" and I am expecting a ");
builder.append(category);
builder.append(".");
} else {
builder.append("\nPerhaps you intended one of the following: ");
Iterator iterator = classNames.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (!iterator.hasNext() && classNames.size() > 1) {
builder.append("or ");
}
builder.append('"');
builder.append(name);
builder.append('"');
builder.append(" ");
}
builder.append("?\n");
}
return new Exception(builder.toString(), ex);
}
}
/**
* control coding parameters for fields. allows code to dictate non-codable
* fields as codable
*/
@Retention(RetentionPolicy.RUNTIME)
public static @interface Set {
boolean codable() default true;
boolean readonly() default false;
boolean writeonly() default false;
boolean required() default false;
boolean intern() default false;
Class extends Validator> validator() default Truthinator.class;
Class extends ClassMap> classMap() default ClassMap.class;
Class extends ClassMapFactory> classMapFactory() default ClassMapFactory.class;
}
static Type[] collectTypes(Class> type, Type node) {
List l = collectTypes(new ArrayList(), type, node);
// System.out.println("collected: " +l);
while (l.size() > 0) {
int ni = l.lastIndexOf(null);
if (ni < 0) {
break;
}
if (ni >= l.size() - 1) {
l.remove(ni);
} else {
l.set(ni, l.get(l.size() - 1));
l.remove(l.size() - 1);
}
}
// System.out.println("returned: " +l);
if (l.size() == 0) {
return null;
} else {
Type t[] = new Type[l.size()];
l.toArray(t);
return t;
}
}
private static List collectTypes(List list, Class> type, Type node) {
if (type == null && node == null) {
return list;
}
if (list == null) {
list = new LinkedList();
}
collectTypes(list, node instanceof Class ? ((Class>) node).getSuperclass() : null, type != null ? type.getGenericSuperclass() : null);
if (node instanceof ParameterizedType) {
List tl = Arrays.asList(((ParameterizedType) node).getActualTypeArguments());
for (Type t : tl) {
list.add((t instanceof Class) || (t instanceof GenericArrayType) ? t : null);
}
}
return list;
}
public static CodableClassInfo getClassFieldMap(Class> clazz) {
CodableClassInfo fieldMap = fieldMaps.get(clazz);
if (fieldMap == null) {
fieldMap = new CodableClassInfo(clazz);
fieldMaps.put(clazz, fieldMap);
}
return fieldMap;
}
public static void flushClassFieldMaps() {
fieldMaps.clear();
}
public static final boolean isNative(Class> type) {
return type == String.class || type == AtomicBoolean.class ||
type == Boolean.class || type.isPrimitive() || Number.class.isAssignableFrom(type);
}
public static Object decodeEnum(Class type, String text) {
return Enum.valueOf(type, text);
}
public static Object decodeNative(Class> type, String text) {
if (type == String.class) {
return text;
} else if (type == Integer.class || type == int.class) {
return text != null ? Integer.parseInt(text) : 0;
} else if (type == Long.class || type == long.class) {
return text != null ? Long.parseLong(text) : 0L;
} else if (type == Boolean.class || type == boolean.class) {
return text != null && Boolean.parseBoolean(text);
} else if (type == Short.class || type == short.class) {
return text != null ? Short.parseShort(text) : 0;
} else if (type == Double.class || type == double.class) {
return text != null ? Double.parseDouble(text) : 0;
} else if (type == Float.class || type == float.class) {
return text != null ? Float.parseFloat(text) : 0;
} else if (type == AtomicLong.class) {
return text != null ? new AtomicLong(Long.parseLong(text)) : new AtomicLong(0);
} else if (type == AtomicInteger.class) {
return text != null ? new AtomicInteger(Integer.parseInt(text)) : new AtomicInteger(0);
} else if (type == AtomicBoolean.class) {
return text != null ? new AtomicBoolean(Boolean.parseBoolean(text)) : new AtomicBoolean(false);
} else if (type.isEnum()) {
return Enum.valueOf((Class) type, text);
} else {
return text;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy