BeanUtil.java

package tools.jackson.databind.util;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import com.fasterxml.jackson.annotation.JsonInclude;

import tools.jackson.databind.DatabindContext;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.cfg.MapperConfig;

/**
 * Helper class that contains functionality needed by both serialization
 * and deserialization side.
 */
public class BeanUtil
{
    /*
    /**********************************************************************
    /* Name mangling
    /**********************************************************************
     */

    /**
     * @deprecated since 3.0.0-rc2 Use {@link tools.jackson.databind.introspect.DefaultAccessorNamingStrategy}
     *    instead
     */
    @Deprecated // since 3.0.0-rc2
    public static String stdManglePropertyName(final String basename, final int offset)
    {
        final int end = basename.length();
        if (end == offset) { // empty name, nope
            return null;
        }
        // first: if it doesn't start with capital, return as-is
        char c0 = basename.charAt(offset);
        char c1 = Character.toLowerCase(c0);
        if (c0 == c1) {
            return basename.substring(offset);
        }
        // 17-Dec-2014, tatu: As per [databind#653], need to follow more
        //   closely Java Beans spec; specifically, if two first are upper-case,
        //   then no lower-casing should be done.
        if ((offset + 1) < end) {
            if (Character.isUpperCase(basename.charAt(offset+1))) {
                return basename.substring(offset);
            }
        }
        StringBuilder sb = new StringBuilder(end - offset);
        sb.append(c1);
        sb.append(basename, offset+1, end);
        return sb.toString();
    }

    /*
    /**********************************************************************
    /* Value defaulting helpers
    /**********************************************************************
     */

    /**
     * Accessor used to find out "default value" to use for comparing values to
     * serialize, to determine whether to exclude value from serialization with
     * inclusion type of {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_DEFAULT}.
     *<p>
     * Default logic is such that for primitives, expected defaults (0 for `int`, `false` for
     * `boolean`) are returned; for primitive wrappers (`Integer`, `Boolean`, etc), either `null`
     * or wrapped default for matching primitive is returned (depending on
     * {@link MapperFeature#WRAPPERS_DEFAULT_TO_NULL} setting);
     * for Strings, empty String;
     * and for structured (Maps, Collections, arrays) and reference types, criteria
     * {@link com.fasterxml.jackson.annotation.JsonInclude.Include#NON_EMPTY}
     * is used (to compare to "empty" value)
     *<p>
     * @param type Type for which default value requested
     * @param wrappersAsNulls If {@code true}, default for primitive wrapper types
     *    like {@link java.lang.Boolean} will be {@code null}; if {@code false} will be
     *    wrapped default of matching primitive type (for {@code java.lang.Boolean} that
     *    would be {@code Boolean.FALSE})
     *
     * @since 3.1
     */
    public static Object propertyDefaultValue(JavaType type, boolean wrappersAsNulls)
    {
        // 06-Nov-2015, tatu: Returning null is fine for Object types; but need special
        //   handling for primitives since they are never passed as nulls.
        final Class<?> cls = type.getRawClass();

        // 11-Jan-2026, tatu: [databind#5570] Primitive types have non-null defaults
        if (cls.isPrimitive()) {
            return ClassUtil.defaultValue(cls);
        }
        if (cls == String.class) {
            return "";
        }
        if (type.isContainerType() || type.isReferenceType()) {
            return JsonInclude.Include.NON_EMPTY;
        }
        // For wrapper types (Integer, Boolean, etc.), default is either null,
        // or the wrapped primitive default
        Class<?> primitiveType = ClassUtil.primitiveType(cls);
        if (primitiveType != null) {
            if (wrappersAsNulls) {
                return null;
            }
            return ClassUtil.defaultValue(primitiveType);
        }
        // 09-Mar-2016, tatu: Not sure how far this path we want to go but for now
        //   let's add `java.util.Date` and `java.util.Calendar`, as per [databind#1550]
        if (type.isTypeOrSubTypeOf(Date.class)) {
            return new Date(0L);
        }
        if (type.isTypeOrSubTypeOf(Calendar.class)) {
            Calendar c = new GregorianCalendar();
            c.setTimeInMillis(0L);
            return c;
        }
        return null;
    }

    /**
     * Short-cut for:
     *<pre>
     * getDefaultValue(type, ctxt.isEnabled(MapperFeature.WRAPPERS_DEFAULT_TO_NUL));
     *</pre>
     *
     * @since 3.1
     */
    public static Object propertyDefaultValue(DatabindContext ctxt, JavaType type)
    {
        return propertyDefaultValue(type,
                ctxt.isEnabled(MapperFeature.WRAPPERS_DEFAULT_TO_NULL));
    }

    /**
     * Short-cut for:
     *<pre>
     * propertyDefaultValue(type, config.isEnabled(MapperFeature.WRAPPERS_DEFAULT_TO_NUL));
     *</pre>
     *
     * @since 3.1
     */
    public static Object propertyDefaultValue(MapperConfig<?> config, JavaType type)
    {
        return propertyDefaultValue(type,
                config.isEnabled(MapperFeature.WRAPPERS_DEFAULT_TO_NULL));
    }
    
    /**
     * @deprecated Since 3.1 use one of {@code propertyDefaultValue()} variants.
     */
    @Deprecated // since 3.1
    public static Object getDefaultValue(JavaType type) {
        return propertyDefaultValue(type, true);
    }
    
    /*
    /**********************************************************************
    /* Package-specific type detection for error handling
    /**********************************************************************
     */

    /**
     * Helper method called by {@link tools.jackson.databind.deser.BeanDeserializerFactory}
     * and {@link tools.jackson.databind.ser.BeanSerializerFactory} to check
     * if given unrecognized type (to be (de)serialized as general POJO) is one of
     * "well-known" types for which there would be a datatype module; and if so,
     * return appropriate failure message to give to caller.
     */
    public static String checkUnsupportedType(MapperConfig<?> config, JavaType type) {
        final String className = type.getRawClass().getName();
        String typeName, moduleName;

        if (isJodaTimeClass(className)) {
            typeName =  "Joda date/time";
            moduleName = "com.fasterxml.jackson.datatype:jackson-datatype-joda";
        } else {
            return null;
        }
        return String.format("%s type %s not supported by default: add Module \"%s\" to enable handling",
                typeName, ClassUtil.getTypeDescription(type), moduleName);
    }
    
    public static boolean isJodaTimeClass(Class<?> rawType) {
        return isJodaTimeClass(rawType.getName());
    }

    private static boolean isJodaTimeClass(String className) {
        return className.startsWith("org.joda.time.");
    }
}