package com.fasterxml.jackson.databind.introspect;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;

/**
 * Tests Scala-style JVM naming patterns for properties.
 *
 * Scala uses identifiers that are legal JVM names, but not legal Java names:
 *
 * <ul>
 *     <li><code>prop␣</code> (trailing space) for fields</li>
 *     <li><code>prop</code> for getters</li>
 *     <li><code>prop_=</code> for setters</li>
 * </ul>
 *
 * Scala sources turn property accesses into method calls in most cases; the
 * backing field and the particulars of the method names are implementation details.
 *
 * Since I can't reproduce them in Java, I've substituted legal but uncommonly
 * used characters as placeholders.
 */
public class TestScalaLikeImplicitProperties extends BaseMapTest
{
    static class NameMangler extends JacksonAnnotationIntrospector
    {
        private static final long serialVersionUID = 1L;

        @Override
        public String findImplicitPropertyName(AnnotatedMember member) {
            String name = null;
            if (member instanceof AnnotatedField) {
                name = member.getName();
                if (name.endsWith("‿")) {
                    return name.substring(0, name.length()-1);
                }
            } else if (member instanceof AnnotatedMethod) {
                name = member.getName();
                if (name.endsWith("_⁀")) {
                    return name.substring(0, name.length()-2);
                }
                if (!name.startsWith("get") && !name.startsWith("set")) {
                    return name;
                }
            } else if (member instanceof AnnotatedParameter) {
                // A placeholder for legitimate property name detection
                // such as what the JDK8 module provides
                return "prop";
            }
            return null;
        }

        /* Deprecated since 2.9
        @Override
        public boolean hasCreatorAnnotation(Annotated a) {
            return (a instanceof AnnotatedConstructor);
        }
        */

        @Override
        public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated a) {
            // A placeholder for legitimate creator detection.
            // In Scala, all primary constructors should be creators,
            // but I can't obtain a reference to the AnnotatedClass from the
            // AnnotatedConstructor, so it's simulated here.
            return (a instanceof AnnotatedConstructor)
                    ? JsonCreator.Mode.DEFAULT : null;
        }
    }

    static class ValProperty
    {
        private final String prop‿;
        public String prop() { return prop‿; }

        public ValProperty(String prop) {
            prop‿ = prop;
        }
    }

    static class ValWithBeanProperty
    {
        private final String prop‿;
        public String prop() { return prop‿; }
        public String getProp() { return prop‿; }

        public ValWithBeanProperty(String prop) {
            prop‿ = prop;
        }
    }

    static class VarProperty
    {
        private String prop‿;
        public String prop() { return prop‿; }
        public void prop_⁀(String p) { prop‿ = p; }

        public VarProperty(String prop) {
            prop‿ = prop;
        }
    }

    static class VarWithBeanProperty
    {
        private String prop‿;
        public String prop() { return prop‿; }
        public void prop_⁀(String p) { prop‿ = p; }
        public String getProp() { return prop‿; }
        public void setProp(String p) { prop‿ = p; }

        public VarWithBeanProperty(String prop) {
            prop‿ = prop;
        }
    }

    static class GetterSetterProperty
    {
        // Different name to represent an arbitrary implementation, not necessarily local to this class.
        private String _prop_impl = "get/set";
        public String prop() { return _prop_impl; }
        public void prop_⁀(String p) { _prop_impl = p; }

        // Getter/Setters are typically not in the constructor because they are implemented
        // by the end user, not the compiler. They should be detected similar to 'bean-style'
        // getProp/setProp pairs.
    }

    /*
    /**********************************************************
    /* Test methods
    /**********************************************************
     */

    public void testValProperty() throws Exception
    {
        ObjectMapper m = manglingMapper();

        assertEquals("{\"prop\":\"val\"}", m.writeValueAsString(new ValProperty("val")));
    }

    public void testValWithBeanProperty() throws Exception
    {
        ObjectMapper m = manglingMapper();

        assertEquals("{\"prop\":\"val\"}", m.writeValueAsString(new ValWithBeanProperty("val")));
    }


    public void testVarProperty() throws Exception
    {
        ObjectMapper m = manglingMapper();

        assertEquals("{\"prop\":\"var\"}", m.writeValueAsString(new VarProperty("var")));
        VarProperty result = m.readValue("{\"prop\":\"read\"}", VarProperty.class);
        assertEquals("read", result.prop());
    }


    public void testVarWithBeanProperty() throws Exception
    {
        ObjectMapper m = manglingMapper();

        assertEquals("{\"prop\":\"var\"}", m.writeValueAsString(new VarWithBeanProperty("var")));
        VarWithBeanProperty result = m.readValue("{\"prop\":\"read\"}", VarWithBeanProperty.class);
        assertEquals("read", result.prop());
    }


    public void testGetterSetterProperty() throws Exception
    {
        ObjectMapper m = manglingMapper();

        assertEquals("{\"prop\":\"get/set\"}", m.writeValueAsString(new GetterSetterProperty()));
        GetterSetterProperty result = m.readValue("{\"prop\":\"read\"}", GetterSetterProperty.class);
        assertEquals("read", result.prop());
    }

    /*
    /**********************************************************
    /* Helper methods
    /**********************************************************
     */
    
    private ObjectMapper manglingMapper()
    {
        ObjectMapper m = new ObjectMapper();
        m.setAnnotationIntrospector(new NameMangler());
        return m;
    }
}
