package org.apache.tapestry5.genericsresolverguava.internal;
//Copyright 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
//
//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.

import java.util.Arrays;
import java.util.Collections;

import org.apache.tapestry5.beaneditor.RelativePosition;
import org.apache.tapestry5.beaneditor.Sortable;
import org.apache.tapestry5.beanmodel.BeanModel;
import org.apache.tapestry5.beanmodel.PropertyConduit;
import org.apache.tapestry5.beanmodel.PropertyModel;
import org.apache.tapestry5.beanmodel.internal.services.PropertyAccessImpl;
import org.apache.tapestry5.beanmodel.services.BeanModelSource;
import org.apache.tapestry5.commons.Messages;
import org.apache.tapestry5.commons.util.UnknownValueException;
import org.apache.tapestry5.ioc.test.IOCTestCase;
import org.easymock.EasyMock;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * Copied from tapestry-core's tests due to the lack of a better option.
* Tests for the bean editor model source itself, as well as the model classes.
*/
public abstract class AbstractBeanModelSourceImplTest extends IOCTestCase
{
 private BeanModelSource source;

 protected abstract BeanModelSource create();
 
 @BeforeClass
 public void setup()
 {
     source = create();
 }

 /**
  * Tests defaults for property names, labels and conduits.
  */
 @Test
 public void default_model_for_bean()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertSame(model.getBeanType(), SimpleBean.class);

     // Based on order of the getter methods (no longer alphabetical)

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age"));

     assertEquals(model.toString(),
             "BeanModel[org.apache.tapestry5.genericsresolverguava.internal.SimpleBean properties:firstName, lastName, age]");

     PropertyModel age = model.get("age");

     assertEquals(age.getLabel(), "Age");
     assertSame(age.getPropertyType(), int.class);
     assertEquals(age.getDataType(), "number");

     PropertyModel firstName = model.get("firstName");

     assertEquals(firstName.getLabel(), "First Name");
     assertEquals(firstName.getPropertyType(), String.class);
     assertEquals(firstName.getDataType(), "text");

     assertEquals(model.get("lastName").getLabel(), "Last Name");

     PropertyConduit conduit = model.get("lastName").getConduit();

     SimpleBean instance = new SimpleBean();

     instance.setLastName("Lewis Ship");

     assertEquals(conduit.get(instance), "Lewis Ship");

     conduit.set(instance, "TapestryDude");

     assertEquals(instance.getLastName(), "TapestryDude");

     // Now, one with some type coercion.

     age.getConduit().set(instance, "40");

     assertEquals(instance.getAge(), 40);

     verify();
 }

 @Test
 public void include_properties()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertSame(model.getBeanType(), SimpleBean.class);

     model.include("lastname", "firstname");

     // Based on order of the getter methods (no longer alphabetical)

     assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "firstName"));

     verify();
 }

 @Test
 public void add_before()
 {
     Messages messages = mockMessages();
     PropertyConduit conduit = mockPropertyConduit();

     Class propertyType = String.class;

     stub_contains(messages, false);

     expect(conduit.getPropertyType()).andReturn(propertyType).atLeastOnce();
     expect(conduit.getAnnotation(EasyMock.isA(Class.class))).andStubReturn(null);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age"));

     // Note the use of case insensitivity here.

     PropertyModel property = model.add(RelativePosition.BEFORE, "lastname", "middleInitial", conduit);

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "middleInitial", "lastName", "age"));

     assertEquals(property.getPropertyName(), "middleInitial");
     assertSame(property.getConduit(), conduit);
     assertSame(property.getPropertyType(), propertyType);

     verify();
 }

 /**
  * TAPESTRY-2202
  */
 @Test
 public void new_instance()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel<SimpleBean> model = source.create(SimpleBean.class, true, messages);

     SimpleBean s1 = model.newInstance();

     assertNotNull(s1);

     SimpleBean s2 = model.newInstance();

     assertNotNull(s2);
     assertNotSame(s1, s2);

     verify();
 }

 @Test
 public void add_before_using_default_conduit()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     model.exclude("firstname");

     assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "age"));

     // Note the use of case insensitivity here.

     PropertyModel property = model.add(RelativePosition.BEFORE, "lastname", "firstName");

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age"));

     assertEquals(property.getPropertyName(), "firstName");
     assertSame(property.getPropertyType(), String.class);

     verify();
 }

 @Test
 public void add_after()
 {
     Messages messages = mockMessages();
     PropertyConduit conduit = mockPropertyConduit();

     Class propertyType = String.class;

     stub_contains(messages, false);

     expect(conduit.getPropertyType()).andReturn(propertyType).atLeastOnce();

     expect(conduit.getAnnotation(EasyMock.isA(Class.class))).andStubReturn(null);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age"));

     PropertyModel property = model.add(RelativePosition.AFTER, "firstname", "middleInitial", conduit);

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "middleInitial", "lastName", "age"));

     assertEquals(property.getPropertyName(), "middleInitial");
     assertSame(property.getConduit(), conduit);
     assertSame(property.getPropertyType(), propertyType);

     verify();
 }

 @Test
 public void filtering_out_read_only_properties()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(ReadOnlyBean.class, true, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("value"));

     model = source.create(ReadOnlyBean.class, false, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("value", "readOnly"));

     verify();
 }

 @Test
 public void non_text_property()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(EnumBean.class, true, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("token"));

     assertEquals(model.get("token").getDataType(), "enum");

     verify();
 }

 @Test
 public void add_duplicate_property_name_is_failure()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     try
     {
         model.add("age");
         unreachable();
     } catch (RuntimeException ex)
     {
         assertEquals(
                 ex.getMessage(),
                 "Bean editor model for org.apache.tapestry5.genericsresolverguava.internal.SimpleBean already contains a property model for property \'age\'.");
     }

     verify();
 }

 @Test
 public void unknown_property_name()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     try
     {
         model.get("frobozz");
         unreachable();
     } catch (UnknownValueException ex)
     {
         assertEquals(
                 ex.getMessage(),
                 "Bean editor model for org.apache.tapestry5.genericsresolverguava.internal.SimpleBean does not contain a property named \'frobozz\'.");

         assertListsEquals(ex.getAvailableValues().getValues(), "age", "firstName", "lastName");
     }

     verify();
 }

 @Test
 public void unknown_property_id()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     model.addEmpty("shrub.foo()");

     try
     {
         model.getById("frobozz");
         unreachable();
     } catch (UnknownValueException ex)
     {
         assertEquals(
                 ex.getMessage(),
                 "Bean editor model for org.apache.tapestry5.genericsresolverguava.internal.SimpleBean does not contain a property with id \'frobozz\'.");

         assertListsEquals(ex.getAvailableValues().getValues(), "age", "firstName", "lastName", "shrubfoo");
     }

     verify();
 }

 @Test
 public void get_added_property_by_name()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     PropertyModel pm = model.addEmpty("shrub.foo()");

     assertSame(model.get("Shrub.Foo()"), pm);

     verify();
 }

 @Test
 public void get_added_property_by_id()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     PropertyModel pm = model.addEmpty("shrub.foo()");

     assertSame(model.getById("ShrubFoo"), pm);

     verify();

 }

 @Test
 public void order_via_annotation()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(StoogeBean.class, true, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("larry", "moe", "shemp", "curly"));

     verify();
 }

 @Test
 public void edit_property_label()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages).get("age").label("Decrepitude").model();

     assertEquals(model.get("age").getLabel(), "Decrepitude");

     verify();
 }

 @Test
 public void label_from_component_messages()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     train_contains(messages, "age-label", true);
     train_get(messages, "age-label", "Decrepitude");

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertEquals(model.get("age").getLabel(), "Decrepitude");

     verify();
 }

 @Test
 public void array_type_bean()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(StringArrayBean.class, true, messages);

     // There's not editor for string arrays yet, so it won't show up normally.

     PropertyModel propertyModel = model.add("array");

     assertSame(propertyModel.getPropertyType(), String[].class);

     String[] value =
             {"foo", "bar"};

     StringArrayBean bean = new StringArrayBean();

     PropertyConduit conduit = propertyModel.getConduit();

     conduit.set(bean, value);

     assertSame(bean.getArray(), value);

     assertSame(conduit.get(bean), value);

     verify();
 }

 @Test
 public void composite_bean()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     train_contains(messages, "simpleage-label", true);
     train_get(messages, "simpleage-label", "Years of Age");

     replay();

     BeanModel model = source.create(CompositeBean.class, true, messages);

     // No editor for CompositeBean, so this will be empty.

     assertEquals(model.getPropertyNames(), Collections.emptyList());

     // There's not editor for string arrays yet, so it won't show up normally.

     PropertyModel firstName = model.add("simple.firstName");

     assertEquals(firstName.getLabel(), "First Name");

     PropertyModel age = model.add("simple.age");
     assertEquals(age.getLabel(), "Years of Age");

     CompositeBean bean = new CompositeBean();

     firstName.getConduit().set(bean, "Fred");
     age.getConduit().set(bean, "97");

     assertEquals(bean.getSimple().getFirstName(), "Fred");
     assertEquals(bean.getSimple().getAge(), 97);

     bean.getSimple().setAge(24);

     assertEquals(age.getConduit().get(bean), new Integer(24));

     verify();
 }

 @Test
 public void default_properties_exclude_write_only()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(WriteOnlyBean.class, false, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("readOnly", "readWrite"));

     verify();
 }

 @Test
 public void add_synthetic_property()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     PropertyModel property = model.addEmpty("placeholder");

     assertFalse(property.isSortable());
     assertSame(property.getPropertyType(), Object.class);
     assertEquals(property.getLabel(), "Placeholder");

     verify();
 }

 @Test
 public void add_missing_property_is_failure()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     try
     {
         model.add("doesNotExist");
         unreachable();
     } catch (Exception ex)
     {
         assertMessageContains(ex, "does not contain", "doesNotExist");
     }

     verify();
 }

 @Test
 public void exclude_property()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertSame(model.exclude("age"), model);

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName"));

     verify();
 }

 @Test
 public void exclude_unknown_property_is_noop()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertSame(model.exclude("frobozz"), model);

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age"));

     verify();
 }

 @Test
 public void nonvisual_properties_are_excluded()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(NonVisualBean.class, true, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("name"));

     verify();
 }

 @Test
 public void reorder()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(SimpleBean.class, true, messages);

     assertSame(model.getBeanType(), SimpleBean.class);

     // Based on order of the getter methods (no longer alphabetical)

     assertEquals(model.getPropertyNames(), Arrays.asList("firstName", "lastName", "age"));

     // Testing a couple of things here:
     // 1) case insensitive
     // 2) unreferenced property names added to the end.

     model.reorder("lastname", "AGE");

     assertEquals(model.getPropertyNames(), Arrays.asList("lastName", "age", "firstName"));

     verify();
 }

 @Test
 public void reoder_from_annotation()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel model = source.create(PropertyOrderBean.class, true, messages);

     assertEquals(model.getPropertyNames(), Arrays.asList("third", "first", "second"));

     verify();
 }

 // https://issues.apache.org/jira/browse/TAP5-1798
 @Test
 public void static_fields_are_ignored()
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel<BeanWithStaticField> model = source.createDisplayModel(BeanWithStaticField.class,  messages);

     assertListsEquals(model.getPropertyNames(), "name");

     verify();
 }
 
 // https://issues.apache.org/jira/browse/TAP5-2305
 @Test
 public void sortable_annotation() 
 {
     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel<SortableBean> model = source.createDisplayModel(SortableBean.class,  messages);
     model.add("nonSortableByDefault");
     model.add("sortable");
     
     // checking whether non-@Sortable annotated properties still behave in the old ways
     assertTrue(model.get("sortableByDefault").isSortable());
     assertFalse(model.get("nonSortableByDefault").isSortable());
     
     // checking @Sortable itself
     assertFalse(model.get("nonSortable").isSortable());
     assertTrue(model.get("sortable").isSortable());

     verify();
 }
 
 // https://issues.apache.org/jira/browse/TAP5-2560
 @Test
 public void missing_property_due_to_wrong_type_parameter_resolution() 
 {

     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel<FileContentUnit> beanModel = source.createDisplayModel(FileContentUnit.class, messages);
     
     // throws "org.apache.tapestry5.internal.services.PropertyExpressionException: Exception generating conduit for expression 'content.mimeType': Class org.apache.tapestry5.internal.services.AbstractBeanModelSourceImplTest$ContentData does not contain a property (or public field) named 'mimeType'." without fix
     beanModel.add("content.mimeType");

     verify();

 }
 
 // https://issues.apache.org/jira/browse/TAP5-2560
 @Test
 public void handling_nested_generics() 
 {

     Messages messages = mockMessages();

     stub_contains(messages, false);

     replay();

     BeanModel<BeanHolder> beanModel = source.createDisplayModel(BeanHolder.class, messages);
     
     // this line would throw an exception
     beanModel.add("bean.value");

     verify();

 }
 
    // https://issues.apache.org/jira/browse/TAP5-2032
    @Test
    public void tap5_2032() 
    {
        // explodes without fix
        new PropertyAccessImpl().getAdapter(ById.class);
    }
 
    public interface IdentifiableEnum<E extends Enum<E>, ID extends Number> 
    {
        ID getId();
    }

    public enum ById implements IdentifiableEnum<ById, Byte> 
    {
        ;
        public Byte getId() 
        {
            return null;
        }
    }
 
 final private static class SortableBean
 {
     private int sortableByDefault;
     private int nonSortable;
     private SimpleBean sortable;
     private SimpleBean nonSortableByDefault;
     
     public int getSortableByDefault()
     {
         return sortableByDefault;
     }
     
     @Sortable(false)
     public int getNonSortable()
     {
         return nonSortable;
     }
     
     @Sortable(true)
     public SimpleBean getSortable()
     {
         return sortable;
     }
     
     public SimpleBean getNonSortableByDefault()
     {
         return nonSortableByDefault;
     }
     
 }
 
    public interface NonTranslatableContentUnit<T extends ContentData> {
        T getContent();
    }

    public interface BinaryContentUnit<T extends BinaryContent> extends NonTranslatableContentUnit<T> {
    }

    public interface FileContentUnit extends BinaryContentUnit<FileContent> {
    }

    public interface ContentData {

        boolean isEmpty();
    }

    public interface BinaryContent extends ContentData {

        String getMimeType();
    }

    public interface FileContent extends BinaryContent {
    }

    public class ConcreteFileContent implements FileContentUnit {

        @Override
        public FileContent getContent() {
            return null;
        }

    }
    
    public class BeanHolder {
        private final Bean bean;

        public BeanHolder(Bean bean) {
            super();
            this.bean = bean;
        }

        
        public Bean getBean() {
            return bean;
        }
        
    }
    
    public class Bean<T> implements Comparable<Bean<T>> {

        private final T value_;

        public Bean(T value) {
            value_ = value;
        }

        @Override
        public int compareTo(Bean<T> o) {
            return 0;
        }

        public T getValue() {
            return value_;
        }
    }

    protected final PropertyConduit mockPropertyConduit()
    {
        return newMock(PropertyConduit.class);
    }

}
