All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.addthis.codec.Codec Maven / Gradle / Ivy

There is a newer version: 3.8.2
Show newest version
/*
 * 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(long version);

        public void bytesDecode(byte b[], long version);
    }

    /**
     * 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 validator() default Truthinator.class;

        Class classMap() default ClassMap.class;

        Class 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