/*
* Copyright (c) 2009, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import com.twelvemonkeys.util.MappedBeanFactory;
import junit.framework.AssertionFailedError;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import java.awt.*;
import java.awt.event.ActionListener;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
/**
* MappedBeanFactoryTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/sandbox/MappedBeanFactoryTestCase.java#1 $
*/
public class MappedBeanFactoryTestCase {
public static interface Foo {
boolean isFoo();
int getBar();
void setBar(int bar);
Rectangle getBounds();
void setBounds(Rectangle bounds);
}
public static interface DefaultFoo extends Foo {
// @MappedBeanFactory.DefaultBooleanValue
@MappedBeanFactory.DefaultValue(booleanValue = false)
boolean isFoo();
@MappedBeanFactory.DefaultIntValue
int getBar();
void setBar(int bar);
Rectangle getBounds();
void setBounds(Rectangle bounds);
@SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"})
DefaultFoo clone();
}
static interface ObservableFoo extends DefaultFoo {
@MappedBeanFactory.DefaultBooleanValue(true)
boolean isFoo();
@MappedBeanFactory.DefaultIntValue(1)
int getBar();
@MappedBeanFactory.NotNull
Rectangle getBounds();
@MappedBeanFactory.Observable
void setBounds(@MappedBeanFactory.NotNull Rectangle bounds);
// TODO: This method should be implicitly supported, and throw IllegalArgument, if NoSuchProperty
// TODO: An observable interface to extend?
void addPropertyChangeListener(String property, PropertyChangeListener listener);
}
@Test
public void testToString() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class);
assertNotNull(foo);
assertNotNull(foo.toString());
assertTrue(foo.toString().contains(DefaultFoo.class.getName()));
// TODO: Consider this:
// assertTrue(foo.toString().contains("foo=false"));
// assertTrue(foo.toString().contains("bar=0"));
// assertTrue(foo.toString().contains("bounds=null"));
}
@Test
public void testClone() {
DefaultFoo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("foo", true));
DefaultFoo clone = foo.clone();
assertNotSame(foo, clone);
assertEquals(foo, clone);
assertEquals(foo.hashCode(), clone.hashCode());
assertEquals(foo.isFoo(), clone.isFoo());
}
@Test
public void testSerializable() throws IOException, ClassNotFoundException {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("foo", true));
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(bytes);
outputStream.writeObject(foo);
ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()));
Foo bar = (Foo) inputStream.readObject();
assertNotSame(foo, bar);
assertEquals(foo, bar);
assertEquals(foo.hashCode(), bar.hashCode());
assertEquals(foo.isFoo(), bar.isFoo());
}
@Test
public void testNotEqualsNull() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class);
@SuppressWarnings({"ObjectEqualsNull"})
boolean equalsNull = foo.equals(null);
assertFalse(equalsNull);
}
@Test
public void testEqualsSelf() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>() {
@SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"})
@Override
public boolean equals(Object o) {
throw new AssertionFailedError("Don't need to test map for equals if same object");
}
});
assertTrue(foo.equals(foo));
}
@Test
public void testEqualsOther() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class);
Foo other = MappedBeanFactory.as(DefaultFoo.class);
assertEquals(foo, other);
}
@Test
public void testEqualsOtherModifiedSameValue() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>());
Foo other = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("bar", 0)));
assertEquals(foo, other);
// No real change
other.setBar(foo.getBar());
assertTrue(foo.equals(other));
}
@Test
public void testNotEqualsOtherModified() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class);
Foo other = MappedBeanFactory.as(DefaultFoo.class);
assertEquals(foo, other);
// Real change
other.setBar(42);
assertFalse(foo.equals(other));
}
@Test
public void testEqualsSubclass() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class);
Foo sub = MappedBeanFactory.as(ObservableFoo.class);
assertEquals(foo, sub);
}
@Test
public void testNotEqualsDifferentValues() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class);
Foo bar = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("bar", true));
assertFalse(foo.equals(bar));
}
@Test
public void testNotEqualsDifferentClass() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class);
ActionListener actionListener = MappedBeanFactory.as(ActionListener.class);
assertFalse(foo.equals(actionListener));
}
@Test
public void testBooleanReadOnly() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("foo", true));
assertNotNull(foo);
assertEquals(true, foo.isFoo());
}
@Test
public void testBooleanEmpty() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>());
assertNotNull(foo);
try {
foo.isFoo();
fail("Expected NullPointerException");
}
catch (NullPointerException expected) {
}
}
@Test
public void testBooleanEmptyWithConverter() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(), new NullBooleanConverter(true));
assertNotNull(foo);
assertEquals(true, foo.isFoo());
}
@Test
public void testIntReadOnly() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("bar", 1));
assertNotNull(foo);
assertEquals(1, foo.getBar());
try {
foo.setBar(42);
fail("Expected UnsupportedOperationException");
}
catch (UnsupportedOperationException expected) {
}
}
@Test
public void testIntReadWrite() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("bar", 1)));
assertNotNull(foo);
assertEquals(1, foo.getBar());
foo.setBar(42);
assertEquals(42, foo.getBar());
}
@Test
public void testIntNull() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("bar", null)));
assertNotNull(foo);
// TODO: Handle null-values smarter, maybe throw a better exception?
// TODO: Consider allowing custom initializers?
try {
foo.getBar();
fail("Expected NullPointerException");
}
catch (NullPointerException expected) {
}
foo.setBar(42);
assertEquals(42, foo.getBar());
}
@Test
public void testIntNullWithConverter() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("bar", null)), new NullIntConverter(1));
assertNotNull(foo);
assertEquals(1, foo.getBar());
foo.setBar(42);
assertEquals(42, foo.getBar());
}
@Test
public void testIntWrongType() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("bar", "1")));
assertNotNull(foo);
// TODO: Handle conversion smarter, maybe throw a better exception?
try {
foo.getBar();
fail("Expected ClassCastException");
}
catch (ClassCastException expected) {
}
// TODO: Should we allow changing type?
try {
foo.setBar(42);
fail("Expected ClassCastException");
}
catch (ClassCastException expected) {
}
}
@Test
public void testBounds() {
Rectangle rectangle = new Rectangle(2, 2, 4, 4);
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("bounds", rectangle)));
assertNotNull(foo);
assertEquals(rectangle, foo.getBounds());
foo.setBounds(new Rectangle());
assertEquals(new Rectangle(), foo.getBounds());
}
@Test
public void testBoundsNull() {
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("bounds", null)));
assertNotNull(foo);
assertNull(foo.getBounds());
Rectangle rectangle = new Rectangle(2, 2, 4, 4);
foo.setBounds(rectangle);
assertEquals(rectangle, foo.getBounds());
foo.setBounds(null);
assertEquals(null, foo.getBounds());
}
@Test
public void testBoundsNullWithConverter() {
// TODO: Allow @NotNull annotations, to say that null is not a valid return value/paramter?
Foo foo = MappedBeanFactory.as(ObservableFoo.class, new HashMap<String, Object>(Collections.singletonMap("bounds", null)), new MappedBeanFactory.Converter<Void, Rectangle>() {
public Class<Void> getFromType() {
return Void.class;
}
public Class<Rectangle> getToType() {
return Rectangle.class;
}
public Rectangle convert(Void value, Rectangle old) {
return new Rectangle(10, 10, 10, 10);
}
});
assertNotNull(foo);
// TODO: The current problem is that null is okay as return value, even if not specified for interface...
assertEquals(new Rectangle(10, 10, 10, 10), foo.getBounds());
Rectangle rectangle = new Rectangle(2, 2, 4, 4);
foo.setBounds(rectangle);
assertEquals(rectangle, foo.getBounds());
}
@Test
public void testBoundsAsMapWithConverter() throws IntrospectionException {
Rectangle rectangle = new Rectangle(2, 2, 4, 4);
Map<String, Object> recAsMap = new HashMap<String, Object>();
recAsMap.put("x", 2);
recAsMap.put("y", 2);
recAsMap.put("width", 4);
recAsMap.put("height", 4);
HashMap<String, Object> map = new HashMap<String, Object>(Collections.singletonMap("bounds", recAsMap));
// TODO: Allow for registering superclasses/interfaces like Map...
Foo foo = MappedBeanFactory.as(DefaultFoo.class, map, new MapRectangleConverter(), new RectangleMapConverter());
assertNotNull(foo);
assertEquals(rectangle, foo.getBounds());
foo.setBounds(new Rectangle());
assertEquals(new Rectangle(), foo.getBounds());
assertEquals(recAsMap, map.get("bounds"));
assertSame(recAsMap, map.get("bounds"));
// TODO: The converter should maybe not have to handle this
foo.setBounds(null);
assertNull(foo.getBounds());
assertEquals(recAsMap, map.get("bounds"));
assertSame(recAsMap, map.get("bounds"));
Rectangle bounds = new Rectangle(1, 1, 1, 1);
foo.setBounds(bounds);
assertEquals(bounds, foo.getBounds());
assertEquals(1, foo.getBounds().x);
assertEquals(1, foo.getBounds().y);
assertEquals(1, foo.getBounds().width);
assertEquals(1, foo.getBounds().height);
assertEquals(recAsMap, map.get("bounds"));
assertSame(recAsMap, map.get("bounds"));
}
@Test
public void testSpeed() {
// How many times faster may the direct access be, before we declare failure?
final int threshold = 50;
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(Collections.singletonMap("foo", false)));
Foo bar = new Foo() {
public boolean isFoo() {
return false;
}
public int getBar() {
throw new UnsupportedOperationException("Method getBar not implemented");
}
public void setBar(int bar) {
throw new UnsupportedOperationException("Method setBar not implemented");
}
public Rectangle getBounds() {
throw new UnsupportedOperationException("Method getBounds not implemented"); // TODO: Implement
}
public void setBounds(Rectangle bounds) {
throw new UnsupportedOperationException("Method setBounds not implemented"); // TODO: Implement
}
};
final int warmup = 50005;
final int iter = 2000000;
for (int i = 0; i < warmup; i++) {
if (foo.isFoo()) {
fail();
}
if (bar.isFoo()) {
fail();
}
}
long startProxy = System.nanoTime();
for (int i = 0; i < iter; i++) {
if (foo.isFoo()) {
fail();
}
}
long proxyTime = System.nanoTime() - startProxy;
long startJava = System.nanoTime();
for (int i = 0; i < iter; i++) {
if (bar.isFoo()) {
fail();
}
}
long javaTime = System.nanoTime() - startJava;
assertTrue(
String.format(
"Proxy time (%1$,d ms) greater than %3$d times direct invocation (%2$,d ms)",
proxyTime / 1000, javaTime / 1000, threshold
),
proxyTime < threshold * javaTime);
}
private static class MapRectangleConverter implements MappedBeanFactory.Converter<HashMap, Rectangle> {
public Class<HashMap> getFromType() {
return HashMap.class;
}
public Class<Rectangle> getToType() {
return Rectangle.class;
}
public Rectangle convert(final HashMap pMap, Rectangle pOldValue) {
if (pMap == null || pMap.isEmpty()) {
return null;
}
Rectangle rectangle = pOldValue != null ? pOldValue : new Rectangle();
rectangle.x = (Integer) pMap.get("x");
rectangle.y = (Integer) pMap.get("y");
rectangle.width = (Integer) pMap.get("width");
rectangle.height = (Integer) pMap.get("height");
return rectangle;
}
}
private static class RectangleMapConverter implements MappedBeanFactory.Converter<Rectangle, HashMap> {
public Class<HashMap> getToType() {
return HashMap.class;
}
public Class<Rectangle> getFromType() {
return Rectangle.class;
}
public HashMap convert(final Rectangle pRectangle, HashMap pOldValue) {
@SuppressWarnings("unchecked")
HashMap<String, Integer> map = pOldValue != null ? pOldValue : new HashMap<String, Integer>();
if (pRectangle != null) {
map.put("x", pRectangle.x);
map.put("y", pRectangle.y);
map.put("width", pRectangle.width);
map.put("height", pRectangle.height);
}
else {
map.remove("x");
map.remove("y");
map.remove("width");
map.remove("height");
}
return map;
}
}
private static class NullIntConverter implements MappedBeanFactory.Converter<Void, Integer> {
private Integer mInitialValue;
public NullIntConverter(int pValue) {
mInitialValue = pValue;
}
public Class<Void> getFromType() {
return Void.class;
}
public Class<Integer> getToType() {
return Integer.class;
}
public Integer convert(Void value, Integer old) {
return mInitialValue;
}
}
private static class NullBooleanConverter implements MappedBeanFactory.Converter<Void, Boolean> {
private Boolean mInitialValue;
public NullBooleanConverter(boolean pValue) {
mInitialValue = pValue;
}
public Class<Void> getFromType() {
return Void.class;
}
public Class<Boolean> getToType() {
return Boolean.class;
}
public Boolean convert(Void value, Boolean old) {
return mInitialValue;
}
}
}