PropertyName.java

package com.fasterxml.jackson.databind;

import java.util.Objects;

import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.core.util.InternCache;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.util.ClassUtil;

/**
 * Simple value class used for containing names of properties as defined
 * by annotations (and possibly other configuration sources).
 *
 * @since 2.1
 */
public class PropertyName
    implements java.io.Serializable
{
    private static final long serialVersionUID = 1L; // 2.5

    private final static String _USE_DEFAULT = "";
    private final static String _NO_NAME = "";

    /**
     * Special placeholder value that indicates that name to use should be
     * based on the standard heuristics. This can be different from returning
     * null, as null means "no information available, whereas this value
     * indicates explicit defaulting.
     */
    public final static PropertyName USE_DEFAULT = new PropertyName(_USE_DEFAULT, null);

    /**
     * Special placeholder value that indicates that there is no name associated.
     * Exact semantics to use (if any) depend on actual annotation in use, but
     * commonly this value disables behavior for which name would be needed.
     */
    public final static PropertyName NO_NAME = new PropertyName(new String(_NO_NAME), null);

    /**
     * Basic name of the property.
     */
    protected final String _simpleName;

    /**
     * Additional namespace, for formats that have such concept (JSON
     * does not, XML does, for example).
     */
    protected final String _namespace;

    /**
     * Lazily-constructed efficient representation of the simple name.
     *<p>
     * NOTE: not defined as volatile to avoid performance problem with
     * concurrent access in multi-core environments; due to statelessness
     * of {@link SerializedString} at most leads to multiple instantiations.
     *
     * @since 2.4
     */
    protected SerializableString _encodedSimple;

    public PropertyName(String simpleName) {
        this(simpleName, null);
    }

    public PropertyName(String simpleName, String namespace)
    {
        _simpleName = ClassUtil.nonNullString(simpleName);
        _namespace = namespace;
    }

    // To support JDK serialization, recovery of Singleton instance
    protected Object readResolve() {
        if (_namespace == null) {
            if (_simpleName == null || _USE_DEFAULT.equals(_simpleName)) {
                return USE_DEFAULT;
            }
            // 30-Oct-2016, tatu: I don't see how this could ever occur...
            //     or how to distinguish USE_DEFAULT/NO_NAME from serialized
            /*
            if (_simpleName.equals(_NO_NAME)) {
                return NO_NAME;
            }
            */
            // 22-Jun-2024, tatu: This is hopeful not problematic as marker
            //   value should only be provided by AnnotationIntrospector etc,
            //   but not stored in Deserializer/Serializer instances.
        }
        return this;
    }

    /**
     * @since 2.6
     */
    public static PropertyName construct(String simpleName)
    {
        if (simpleName == null || simpleName.isEmpty()) {
            return USE_DEFAULT;
        }
        return new PropertyName(InternCache.instance.intern(simpleName), null);
    }

    public static PropertyName construct(String simpleName, String ns)
    {
        if (simpleName == null) {
            simpleName = "";
        }
        if (ns == null && simpleName.isEmpty()) {
            return USE_DEFAULT;
        }
        return new PropertyName(InternCache.instance.intern(simpleName), ns);
    }

    /**
     * Method that will combine information from two {@link PropertyName}
     * instances
     *
     * @param name1 Name with higher precedence; may be {@code null}
     * @param name2 Name with lower precedence; may be {@code null}
     *
     * @return Merged information; only {@code null} if both arguments
     *   are {@code null}s.
     *
     * @since 2.17
     */
    public static PropertyName merge(PropertyName name1, PropertyName name2) {
        if (name1 == null) {
            return name2;
        }
        if (name2 == null) {
            return name1;
        }
        // 22-Jun-2024, tatu: [databind#4595] Should not merge NO_NAME
        if (name1 == NO_NAME) {
            return name1;
        }
        String ns = _nonEmpty(name1._namespace, name2._namespace);
        String simple = _nonEmpty(name1._simpleName, name2._simpleName);

        // But see if we can just return one of arguments as-is:
        if (ns == name1._namespace && simple == name1._simpleName) {
            return name1;
        }
        if (ns == name2._namespace && simple == name2._simpleName) {
            return name2;
        }
        return construct(simple, ns);
    }

    private static String _nonEmpty(String str1, String str2) {
        if (str1 == null) {
            return str2;
        }
        if (str2 == null) {
            return str1;
        }
        if (str1.isEmpty()) {
            return str2;
        }
        return str1;
    }

    public PropertyName internSimpleName()
    {
        if (_simpleName.isEmpty()) { // empty String is canonical already
            return this;
        }
        String interned = InternCache.instance.intern(_simpleName);
        if (interned == _simpleName) { // was already interned
            return this;
        }
        return new PropertyName(interned, _namespace);
    }

    /**
     * Fluent factory method for constructing an instance with different
     * simple name.
     */
    public PropertyName withSimpleName(String simpleName)
    {
        if (simpleName == null) {
            simpleName = "";
        }
        if (simpleName.equals(_simpleName)) {
            return this;
        }
        return new PropertyName(simpleName, _namespace);
    }

    /**
     * Fluent factory method for constructing an instance with different
     * namespace.
     */
    public PropertyName withNamespace(String ns) {
        if (ns == null) {
            if (_namespace == null) {
                return this;
            }
        } else if (ns.equals(_namespace)) {
            return this;
        }
        return new PropertyName(_simpleName, ns);
    }

    /*
    /**********************************************************
    /* Accessors
    /**********************************************************
     */

    public String getSimpleName() {
        return _simpleName;
    }

    /**
     * Accessor that may be used to get lazily-constructed efficient
     * representation of the simple name.
     *
     * @since 2.4
     */
    public SerializableString simpleAsEncoded(MapperConfig<?> config) {
        SerializableString sstr = _encodedSimple;
        if (sstr == null) {
            if (config == null) {
                sstr = new SerializedString(_simpleName);
            } else {
                sstr = config.compileString(_simpleName);
            }
            _encodedSimple = sstr;
        }
        return sstr;
    }

    public String getNamespace() {
        return _namespace;
    }

    public boolean hasSimpleName() {
        return !_simpleName.isEmpty();
    }

    /**
     * @since 2.3
     */
    public boolean hasSimpleName(String str) {
        // _simpleName never null so...
        return _simpleName.equals(str);
    }

    public boolean hasNamespace() {
        return _namespace != null;
    }

    /**
     * Method that is basically equivalent of:
     *<pre>
     *   !hasSimpleName() &lt;&lt; !hasNamespace();
     *</pre>
     *
     * @since 2.4
     */
    public boolean isEmpty() {
        return (_namespace == null) && (_simpleName.isEmpty());
    }

    /*
    /**********************************************************
    /* Std method overrides
    /**********************************************************
     */

    @Override
    public boolean equals(Object o)
    {
        if (o == this) return true;
        if (o == null) return false;
        // 13-Nov-2012, tatu: by default, require strict type equality.
        //   Re-evaluate if this becomes an issue.
        if (o.getClass() != getClass()) return false;
        // 13-Nov-2012, tatu: Should we have specific rules on matching USE_DEFAULT?
        //   (like, it only ever matching exact instance)
        //   If we did, would need to check symmetrically; that is, if either 'this'
        //   or 'o' was USE_DEFAULT, both would have to be.
        PropertyName other = (PropertyName) o;
        if (_simpleName == null) {
            if (other._simpleName != null) return false;
        } else if (!_simpleName.equals(other._simpleName)) {
            return false;
        }
        if (_namespace == null) {
            return (null == other._namespace);
        }
        return _namespace.equals(other._namespace);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(_simpleName) * 31
                + Objects.hashCode(_namespace);
    }

    @Override
    public String toString() {
        if (_namespace == null) {
            return _simpleName;
        }
        return "{"+_namespace + "}" + _simpleName;
    }
}