blob: 155c49925ea8141307a4b9c6ea8aabfd1d8c5e02 [file] [log] [blame]
// Copyright 2006, 2007, 2008, 2010 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.
package org.apache.tapestry5.ioc.internal.services;
import org.apache.tapestry5.beaneditor.DataType;
import org.apache.tapestry5.beaneditor.Validate;
import org.apache.tapestry5.ioc.Registry;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.ioc.internal.IOCInternalTestCase;
import org.apache.tapestry5.ioc.internal.util.Pair;
import org.apache.tapestry5.ioc.internal.util.StringLongPair;
import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
import org.apache.tapestry5.ioc.services.PropertyAccess;
import org.apache.tapestry5.ioc.services.PropertyAdapter;
import org.testng.annotations.Test;
import java.awt.*;
import java.beans.*;
import java.util.Arrays;
import java.util.Random;
public class PropertyAccessImplTest extends IOCInternalTestCase
{
private static final String CLASS_NAME = PropertyAccessImplTest.class.getName();
private PropertyAccess access = new PropertyAccessImpl();
private Random random = new Random();
public static class Bean
{
public static Double PI = 3.14;
@DataType("fred")
@Validate("field-value-overridden")
private int value;
@Validate("getter-value-overrides")
public int getValue()
{
return value;
}
public void setValue(int value)
{
this.value = value;
}
@Override
public String toString()
{
return "PropertyUtilsTestBean";
}
public void setWriteOnly(boolean b)
{
}
public String getReadOnly()
{
return null;
}
}
public static class BeanSubclass extends Bean
{
}
public static class ExceptionBean
{
public boolean getFailure()
{
throw new RuntimeException("getFailure");
}
public void setFailure(boolean b)
{
throw new RuntimeException("setFailure");
}
@Override
public String toString()
{
return "PropertyUtilsExceptionBean";
}
}
public static class UglyBean
{
}
public static class UglyBeanBeanInfo implements BeanInfo
{
public BeanInfo[] getAdditionalBeanInfo()
{
return new BeanInfo[0];
}
public BeanDescriptor getBeanDescriptor()
{
return null;
}
public int getDefaultEventIndex()
{
return 0;
}
public int getDefaultPropertyIndex()
{
return 0;
}
public EventSetDescriptor[] getEventSetDescriptors()
{
return new EventSetDescriptor[0];
}
public Image getIcon(int iconKind)
{
return null;
}
public MethodDescriptor[] getMethodDescriptors()
{
return new MethodDescriptor[0];
}
public PropertyDescriptor[] getPropertyDescriptors()
{
throw new RuntimeException("This is the UglyBean.");
}
}
public static class ScalaBean
{
private String value;
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
public String value()
{
return value;
}
public void value_$eq(String value)
{
this.value = value;
}
}
public static class ScalaClass
{
private String value;
public String value()
{
return value;
}
public void value_$eq(String value)
{
this.value = value;
}
}
public static class BooleanHolder
{
private boolean flag;
public boolean isFlag()
{
return flag;
}
public void setFlag(boolean flag)
{
this.flag = flag;
}
}
public static class PublicFieldBean
{
public String value;
}
public static class ShadowedPublicFieldBean
{
private String _value;
public String value;
public String getValue()
{
return _value;
}
public void setValue(String value)
{
_value = value;
}
}
public static abstract class GenericBean<T>
{
public T value;
}
public static class GenericStringBean extends GenericBean<String>
{
}
public static interface BeanInterface
{
String getValue();
void setValue(String v);
String getOtherValue();
void setOtherValue(String v);
int getIntValue(); // read-only
}
public static abstract class AbstractBean implements BeanInterface
{
// abstract class implements method from interface
private String other;
public String getOtherValue()
{
return other;
}
public void setOtherValue(String v)
{
other = v;
}
}
public static class ConcreteBean extends AbstractBean
{
private String value;
private int intValue;
public ConcreteBean(int intValue) {
this.intValue = intValue;
}
public String getValue() {
return value;
}
public void setValue(String v) {
value = v;
}
public int getIntValue() {
return intValue;
}
}
@Test
public void simple_read_access()
{
Bean b = new Bean();
int value = random.nextInt();
b.setValue(value);
assertEquals(access.get(b, "value"), value);
}
@Test
public void property_name_case_is_ignored_on_read()
{
Bean b = new Bean();
int value = random.nextInt();
b.setValue(value);
assertEquals(access.get(b, "VALUE"), value);
}
@Test
public void simple_write_access()
{
Bean b = new Bean();
int value = random.nextInt();
access.set(b, "value", value);
assertEquals(b.getValue(), value);
}
@Test
public void property_name_case_is_ignored_on_write()
{
Bean b = new Bean();
int value = random.nextInt();
access.set(b, "VALUE", value);
assertEquals(b.getValue(), value);
}
@Test
public void missing_property()
{
Bean b = new Bean();
try
{
access.get(b, "zaphod");
unreachable();
}
catch (IllegalArgumentException ex)
{
assertEquals(ex.getMessage(), "Class " + CLASS_NAME + "$Bean does not "
+ "contain a property named 'zaphod'.");
}
}
@Test
public void attempt_to_update_read_only_property()
{
Bean b = new Bean();
try
{
access.set(b, "class", null);
unreachable();
}
catch (UnsupportedOperationException ex)
{
assertEquals(ex.getMessage(), "Class " + CLASS_NAME
+ "$Bean does not provide an mutator ('setter') method for property 'class'.");
}
}
@Test
public void attempt_to_read_from_write_only_property()
{
Bean b = new Bean();
try
{
access.get(b, "writeOnly");
unreachable();
}
catch (UnsupportedOperationException ex)
{
assertEquals(ex.getMessage(), "Class " + CLASS_NAME
+ "$Bean does not provide an accessor ('getter') method for property 'writeOnly'.");
}
}
@Test
public void exception_thrown_inside_getter()
{
ExceptionBean b = new ExceptionBean();
try
{
access.get(b, "failure");
unreachable();
}
catch (RuntimeException ex)
{
assertEquals(ex.getMessage(), "Error reading property 'failure' of PropertyUtilsExceptionBean: getFailure");
}
}
@Test
public void exception_thrown_inside_setter()
{
ExceptionBean b = new ExceptionBean();
try
{
access.set(b, "failure", false);
unreachable();
}
catch (RuntimeException ex)
{
assertEquals(ex.getMessage(), "Error updating property 'failure' of PropertyUtilsExceptionBean: setFailure");
}
}
@Test
public void failure_when_introspecting_class()
{
UglyBean b = new UglyBean();
try
{
access.get(b, "google");
unreachable();
}
catch (RuntimeException ex)
{
assertEquals(ex.getMessage(), "java.lang.RuntimeException: This is the UglyBean.");
}
}
@Test
public void clear_wipes_internal_cache()
{
ClassPropertyAdapter cpa1 = access.getAdapter(Bean.class);
assertSame(cpa1.getBeanType(), Bean.class);
ClassPropertyAdapter cpa2 = access.getAdapter(Bean.class);
assertSame(cpa2, cpa1);
access.clearCache();
ClassPropertyAdapter cpa3 = access.getAdapter(Bean.class);
assertNotSame(cpa3, cpa1);
}
@Test
public void class_property_adapter_toString()
{
ClassPropertyAdapter cpa = access.getAdapter(Bean.class);
assertEquals(cpa.toString(), "<ClassPropertyAdaptor " + CLASS_NAME
+ "$Bean : class, readOnly, value, writeOnly>");
}
@Test
public void property_adapter_read_only_property()
{
ClassPropertyAdapter cpa = access.getAdapter(Bean.class);
PropertyAdapter pa = cpa.getPropertyAdapter("readOnly");
assertTrue(pa.isRead());
assertFalse(pa.isUpdate());
assertFalse(pa.isCastRequired());
assertNull(pa.getWriteMethod());
assertEquals(pa.getReadMethod(), findMethod(Bean.class, "getReadOnly"));
}
@Test
public void property_adapter_write_only_property()
{
ClassPropertyAdapter cpa = access.getAdapter(Bean.class);
PropertyAdapter pa = cpa.getPropertyAdapter("writeOnly");
assertFalse(pa.isRead());
assertTrue(pa.isUpdate());
assertEquals(pa.getWriteMethod(), findMethod(Bean.class, "setWriteOnly"));
assertNull(pa.getReadMethod());
}
@Test
public void class_property_adapter_returns_null_for_unknown_property()
{
ClassPropertyAdapter cpa = access.getAdapter(Bean.class);
assertNull(cpa.getPropertyAdapter("google"));
}
@Test
public void access_to_property_type()
{
ClassPropertyAdapter cpa = access.getAdapter(Bean.class);
assertEquals(cpa.getPropertyAdapter("value").getType(), int.class);
assertEquals(cpa.getPropertyAdapter("readOnly").getType(), String.class);
assertEquals(cpa.getPropertyAdapter("writeOnly").getType(), boolean.class);
}
@Test
public void property_names()
{
ClassPropertyAdapter cpa = access.getAdapter(Bean.class);
assertEquals(cpa.getPropertyNames(), Arrays.asList("class", "readOnly", "value", "writeOnly"));
}
@Test
public void integration()
{
Registry registry = buildRegistry();
PropertyAccess pa = registry.getService("PropertyAccess", PropertyAccess.class);
Bean b = new Bean();
int value = random.nextInt();
pa.set(b, "value", value);
assertEquals(b.getValue(), value);
registry.shutdown();
}
@Test
public void super_interface_methods_inherited_by_sub_interface()
{
ClassPropertyAdapter cpa = access.getAdapter(SubInterface.class);
assertEquals(cpa.getPropertyNames(), Arrays.asList("grandParentProperty", "parentProperty", "subProperty"));
}
@Test
public void indexed_properties_are_ignored()
{
ClassPropertyAdapter cpa = access.getAdapter(BeanWithIndexedProperty.class);
assertEquals(cpa.getPropertyNames(), Arrays.asList("class", "primitiveProperty"));
}
@Test
public void get_annotation_when_annotation_not_present()
{
PropertyAdapter pa = access.getAdapter(AnnotatedBean.class).getPropertyAdapter("readWrite");
assertNull(pa.getAnnotation(Scope.class));
}
@Test
public void get_annotation_with_annotation_on_write_method()
{
PropertyAdapter pa = access.getAdapter(AnnotatedBean.class).getPropertyAdapter("annotationOnWrite");
Scope annotation = pa.getAnnotation(Scope.class);
assertNotNull(annotation);
assertEquals(annotation.value(), "onwrite");
}
@Test
public void read_method_annotation_overrides_write_method_annotation()
{
PropertyAdapter pa = access.getAdapter(AnnotatedBean.class).getPropertyAdapter("annotationOnRead");
Scope annotation = pa.getAnnotation(Scope.class);
assertNotNull(annotation);
assertEquals(annotation.value(), "onread");
}
@Test
public void no_write_method_reading_missing_annotation()
{
PropertyAdapter pa = access.getAdapter(AnnotatedBean.class).getPropertyAdapter("readOnly");
assertNull(pa.getAnnotation(Scope.class));
}
@Test
public void get_annotation_will_read_field()
{
PropertyAdapter pa = access.getAdapter(Bean.class).getPropertyAdapter("value");
DataType dt = pa.getAnnotation(DataType.class);
assertNotNull(dt);
assertEquals(dt.value(), "fred");
}
/**
* TAPESTRY-2448
*/
@Test
public void get_annotation_will_read_inherited_field()
{
PropertyAdapter pa = access.getAdapter(BeanSubclass.class).getPropertyAdapter("value");
DataType dt = pa.getAnnotation(DataType.class);
assertNotNull(dt);
assertEquals(dt.value(), "fred");
}
@Test
public void field_annotation_overridden_by_getter_annotation()
{
PropertyAdapter pa = access.getAdapter(Bean.class).getPropertyAdapter("value");
assertEquals(pa.getAnnotation(Validate.class).value(), "getter-value-overrides");
}
@Test
public void using_generics()
{
ClassPropertyAdapter cpa1 = access.getAdapter(StringLongPair.class);
PropertyAdapter pa1 = cpa1.getPropertyAdapter("key");
assertSame(pa1.getType(), String.class);
assertTrue(pa1.isCastRequired());
assertSame(pa1.getDeclaringClass(), Pair.class);
PropertyAdapter pa2 = cpa1.getPropertyAdapter("value");
assertSame(pa2.getType(), Long.class);
assertTrue(pa2.isCastRequired());
// On the base class, which defines the generic parameter type variables,
// the properties just look like Object.
ClassPropertyAdapter cpa2 = access.getAdapter(Pair.class);
pa1 = cpa2.getPropertyAdapter("key");
assertSame(pa1.getType(), Object.class);
assertFalse(pa1.isCastRequired());
pa2 = cpa2.getPropertyAdapter("value");
assertSame(pa2.getType(), Object.class);
assertFalse(pa2.isCastRequired());
}
@Test
public void get_scala_properties_with_bean_accessors()
{
PropertyAdapter pa = access.getAdapter(ScalaBean.class).getPropertyAdapter("value");
// even thought scala accessors are present the java bean ones should be the ones used by Tapestry
assertEquals(pa.getReadMethod().getName(), "getValue");
assertEquals(pa.getWriteMethod().getName(), "setValue");
}
@Test
public void get_scala_properties()
{
PropertyAdapter pa = access.getAdapter(ScalaClass.class).getPropertyAdapter("value");
assertEquals(pa.getReadMethod().getName(), "value");
assertEquals(pa.getWriteMethod().getName(), "value_$eq");
}
@Test
public void access_to_public_field()
{
PropertyAdapter pa = access.getAdapter(PublicFieldBean.class).getPropertyAdapter("value");
assertTrue(pa.isField());
assertTrue(pa.isRead());
assertTrue(pa.isUpdate());
PublicFieldBean bean = new PublicFieldBean();
pa.set(bean, "fred");
assertEquals(bean.value, "fred");
bean.value = "barney";
assertEquals(pa.get(bean), "barney");
}
@Test
public void property_is_favored_over_public_field()
{
PropertyAdapter pa = access.getAdapter(ShadowedPublicFieldBean.class).getPropertyAdapter("value");
assertFalse(pa.isField());
ShadowedPublicFieldBean bean = new ShadowedPublicFieldBean();
pa.set(bean, "fred");
assertNull(bean.value);
bean.value = "barney";
bean.setValue("wilma");
assertEquals(pa.get(bean), "wilma");
}
@Test
public void access_property_from_unimplemented_interface_in_abstract_base_class()
{
AbstractBean bean = new ConcreteBean(33);
PropertyAdapter valueAdapter = access.getAdapter(AbstractBean.class).getPropertyAdapter("value");
assertNotNull(valueAdapter);
assertFalse(valueAdapter.isField());
valueAdapter.set(bean, "Hello");
assertSame(valueAdapter.get(bean), "Hello");
assertSame(bean.getValue(), "Hello");
PropertyAdapter otherValueAdapter = access.getAdapter(AbstractBean.class).getPropertyAdapter("otherValue");
assertNotNull(otherValueAdapter);
assertFalse(otherValueAdapter.isField());
otherValueAdapter.set(bean, "Other Value");
assertSame(otherValueAdapter.get(bean), "Other Value");
assertSame(bean.getOtherValue(), "Other Value");
PropertyAdapter intValueAdapter = access.getAdapter(AbstractBean.class).getPropertyAdapter("intvalue");
assertNotNull(intValueAdapter);
assertEquals(intValueAdapter.get(bean), 33);
assertTrue(intValueAdapter.isRead());
assertFalse(intValueAdapter.isUpdate());
}
@Test
public void generic_field_is_recognized()
{
PropertyAdapter pa = access.getAdapter(GenericStringBean.class).getPropertyAdapter("value");
assertTrue(pa.isCastRequired());
assertEquals(pa.getType(), String.class);
assertSame(pa.getDeclaringClass(), GenericBean.class);
}
}