/*
* Copyright 2011 Robert W. Vawter III <bob@vawter.org>
*
* 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.jsonddl.impl;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.jsonddl.JsonDdlObject;
import org.jsonddl.JsonDdlVisitor;
import org.jsonddl.JsonDdlVisitor.Context;
import org.jsonddl.impl.ContextImpl.ListContext;
import org.jsonddl.impl.ContextImpl.MapContext;
import org.jsonddl.impl.ContextImpl.PropertyContext;
import org.jsonddl.impl.ContextImpl.ValueContext;
import org.jsonddl.model.Kind;
import org.junit.Test;
/**
* Tests of the various {@link Context} types.
*/
public class ContextImplTest {
interface FooBuilder extends JsonDdlObject.Builder<FooBuilder>, Traversable<FooBuilder> {}
@Test
public void testListContext() {
final FooBuilder foo1 = makeFoo();
final FooBuilder foo2 = makeFoo();
List<FooBuilder> list = new ArrayList<FooBuilder>();
list.add(foo1);
list.add(foo2);
// Simulate traversing list properties of a builder that was created from an immutable object
list = Protected.object(list);
ListContext<FooBuilder> ctx = new ListContext.Builder<FooBuilder>()
.withKind(Kind.LIST)
.withLeafType(FooBuilder.class)
.withMutability(true)
.withNestedKinds(Arrays.asList(Kind.DDL))
.withProperty("someList")
.withValue(list)
.build();
// Insert before and after foo1, removing foo2
class V implements JsonDdlVisitor {
FooBuilder after;
FooBuilder before;
Set<FooBuilder> seen = new HashSet<FooBuilder>();
FooBuilder replacement;
@SuppressWarnings("unused")
public void endVisit(FooBuilder x, Context<FooBuilder> ctx) {
assertEquals(Kind.DDL, ctx.getKind());
assertEquals(FooBuilder.class, ctx.getLeafType());
assertEquals("someList", ctx.getProperty());
assertTrue("Re-traversing already-seen object", seen.add(x));
assertTrue(ctx.canInsert());
assertTrue(ctx.canRemove());
assertTrue(ctx.canReplace());
// Make sure bad casts don't happen later
@SuppressWarnings({ "rawtypes", "unchecked" })
Context<Object> rawContext = (Context) ctx;
if (x == foo1) {
before = makeFoo();
ctx.insertBefore(before);
after = makeFoo();
ctx.insertAfter(after);
try {
rawContext.insertAfter(new Object());
fail();
} catch (ClassCastException expected) {}
try {
rawContext.insertAfter(new Object());
fail();
} catch (ClassCastException expected) {}
} else if (x == after) {
replacement = makeFoo();
ctx.replace(replacement);
try {
rawContext.replace(new Object());
fail();
} catch (ClassCastException expected) {}
} else if (x == foo2) {
ctx.remove();
} else {
fail("Saw unexpecetd list element");
}
}
}
V v = new V();
list = ctx.traverse(v);
assertEquals(Arrays.asList(v.before, foo1, v.replacement), list);
verify(foo1, foo2, v.after, v.before, v.replacement);
}
@Test
public void testMapContext() {
final FooBuilder foo1 = makeFoo();
final FooBuilder foo2 = makeFoo();
Map<String, FooBuilder> map = new TreeMap<String, FooBuilder>();
map.put("foo1", foo1);
map.put("foo2", foo2);
// Simulate traversing properties of a builder that was created from an immutable object
map = Protected.object(map);
MapContext<FooBuilder> ctx = new MapContext.Builder<FooBuilder>()
.withKind(Kind.LIST)
.withLeafType(FooBuilder.class)
.withMutability(true)
.withNestedKinds(Arrays.asList(Kind.DDL))
.withProperty("someList")
.withValue(map)
.build();
// Replace foo1, remove foo2
class V implements JsonDdlVisitor {
Set<FooBuilder> seen = new HashSet<FooBuilder>();
FooBuilder replacement;
@SuppressWarnings("unused")
public void endVisit(FooBuilder x, Context<FooBuilder> ctx) {
assertEquals(Kind.DDL, ctx.getKind());
assertEquals(FooBuilder.class, ctx.getLeafType());
assertEquals("someList", ctx.getProperty());
assertTrue("Re-traversing already-seen object", seen.add(x));
assertFalse(ctx.canInsert());
assertTrue(ctx.canRemove());
assertTrue(ctx.canReplace());
// Make sure bad casts don't happen later
@SuppressWarnings({ "rawtypes", "unchecked" })
Context<Object> rawContext = (Context) ctx;
if (x == foo1) {
replacement = makeFoo();
ctx.replace(replacement);
try {
rawContext.replace(new Object());
fail();
} catch (ClassCastException expected) {}
} else if (x == foo2) {
ctx.remove();
} else {
fail("Saw unexpecetd list element");
}
}
}
V v = new V();
map = ctx.traverse(v);
assertEquals(Collections.singletonMap("foo1", v.replacement), map);
verify(foo1, foo2, v.replacement);
}
/**
* Tests {@link PropertyContext} and {@link ValueContext}.
*/
@Test
public void testValueContextImmutable() {
ValueContext<Boolean> ctx = new ValueContext.Builder<Boolean>().withKind(Kind.BOOLEAN)
.withLeafType(Boolean.class).withProperty("property").withValue(true).build();
assertEquals(Kind.BOOLEAN, ctx.getKind());
assertEquals("property", ctx.getProperty());
assertEquals(Boolean.class, ctx.getLeafType());
assertTrue(ctx.getNestedKinds().isEmpty());
try {
ctx.getNestedKinds().add(Kind.DDL);
fail();
} catch (UnsupportedOperationException expected) {}
try {
assertFalse(ctx.canInsert());
ctx.insertBefore(false);
fail();
} catch (UnsupportedOperationException expected) {}
try {
assertFalse(ctx.canInsert());
ctx.insertAfter(false);
fail();
} catch (UnsupportedOperationException expected) {}
try {
assertFalse(ctx.canReplace());
ctx.replace(false);
fail();
} catch (UnsupportedOperationException expected) {}
try {
assertFalse(ctx.canRemove());
ctx.remove();
fail();
} catch (UnsupportedOperationException expected) {}
assertTrue(ctx.traverse(null));
}
/**
* Tests {@link PropertyContext} and {@link ValueContext}.
*/
@Test
public void testValueContextMutable() {
ValueContext<Boolean> ctx = new ValueContext.Builder<Boolean>().withKind(Kind.BOOLEAN)
.withLeafType(Boolean.class).withMutability(true).withProperty("property").withValue(true)
.build();
assertEquals(Kind.BOOLEAN, ctx.getKind());
assertEquals("property", ctx.getProperty());
assertEquals(Boolean.class, ctx.getLeafType());
assertTrue(ctx.getNestedKinds().isEmpty());
try {
ctx.getNestedKinds().add(Kind.DDL);
fail();
} catch (UnsupportedOperationException expected) {}
try {
assertFalse(ctx.canInsert());
ctx.insertBefore(false);
fail();
} catch (UnsupportedOperationException expected) {}
try {
assertFalse(ctx.canInsert());
ctx.insertAfter(false);
fail();
} catch (UnsupportedOperationException expected) {}
assertTrue(ctx.canReplace());
ctx.replace(false);
// Make sure bad casts don't happen later
@SuppressWarnings({ "rawtypes", "unchecked" })
Context<Object> rawContext = (Context) ctx;
try {
rawContext.replace(new Object());
fail();
} catch (ClassCastException expected) {}
try {
assertFalse(ctx.canRemove());
ctx.remove();
fail();
} catch (UnsupportedOperationException expected) {}
assertFalse(ctx.traverse(null));
}
private FooBuilder makeFoo() {
FooBuilder toReturn = createMock(FooBuilder.class);
expect(toReturn.build()).andReturn(toReturn).anyTimes();
expect(toReturn.builder()).andReturn(toReturn).anyTimes();
expect(toReturn.getDdlObjectType()).andReturn(FooBuilder.class).anyTimes();
expect(toReturn.traverse(anyObject(JsonDdlVisitor.class))).andReturn(toReturn).anyTimes();
replay(toReturn);
return toReturn;
}
}