package com.getperka.flatpack.visitors; /* * #%L * FlatPack serialization code * %% * Copyright (C) 2012 - 2013 Perka Inc. * %% * 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. * #L% */ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; 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.List; import javax.inject.Inject; import org.junit.Test; import com.getperka.flatpack.FlatPackTest; import com.getperka.flatpack.FlatPackVisitor; import com.getperka.flatpack.Visitors; import com.getperka.flatpack.codexes.ValueCodex; import com.getperka.flatpack.ext.Codex; import com.getperka.flatpack.ext.DeserializationContext; import com.getperka.flatpack.ext.SerializationContext; import com.getperka.flatpack.ext.Type; import com.getperka.flatpack.ext.VisitorContext; import com.google.gson.JsonElement; /** * Verify behaviors of standard {@link VisitorContext} implementations. */ public class VisitorContextTest extends FlatPackTest { /** * Just used as a trivial codex to pump {@link FlatPackVisitor#visitValue}. */ static class PassthroughCodex extends ValueCodex<Object> { @Override public Type describe() { return null; } @Override public Object readNotNull(JsonElement element, DeserializationContext context) throws Exception { return null; } @Override public void writeNotNull(Object object, SerializationContext context) throws Exception {} } static class ValueRecorder extends FlatPackVisitor { final List<Object> values = new ArrayList<Object>(); @Override public <T> void endVisitValue(T value, Codex<T> codex, VisitorContext<T> ctx) { values.add(value); } @Override public <T> boolean visitValue(T value, Codex<T> codex, VisitorContext<T> ctx) { values.add(value); return false; } } @Inject PassthroughCodex passthrough; @Inject Visitors visitors; @Test public void testArrayContext() { final Object replacement = "c"; ValueRecorder recorder = new ValueRecorder() { @Override public <T> boolean visitValue(T value, Codex<T> codex, VisitorContext<T> ctx) { assertTrue(ctx.canReplace()); ctx.replace(codex.cast(replacement)); return super.visitValue(value, codex, ctx); } }; Object[] array = new Object[] { "a", "b" }; visitors.getWalkers().walkArray(passthrough).accept(recorder, array); assertEquals(Arrays.asList("a", "a", "b", "b"), recorder.values); assertEquals(Arrays.asList("c", "c"), Arrays.asList(array)); } /** * Test the default behaviors of the VisitorContext base type so other tests can concentrate on * subclass-specific behaviors. */ @Test public void testImmutableContext() { VisitorContext<?> ctx = (VisitorContext<?>) visitors.getWalkers().walkImmutable(passthrough); assertFalse(ctx.canInsert()); assertFalse(ctx.canRemove()); assertFalse(ctx.canReplace()); assertFalse(ctx.didInsert()); assertFalse(ctx.didRemove()); assertFalse(ctx.didReplace()); try { ctx.insertAfter(null); fail(); } catch (UnsupportedOperationException expected) {} try { ctx.insertBefore(null); fail(); } catch (UnsupportedOperationException expected) {} try { ctx.remove(); fail(); } catch (UnsupportedOperationException expected) {} try { ctx.replace(null); fail(); } catch (UnsupportedOperationException expected) {} ValueRecorder recorder = new ValueRecorder(); Object value = new Object(); ctx.walkImmutable(passthrough).accept(recorder, value); assertEquals(Arrays.asList(value, value), recorder.values); } @Test public void testIterableContext() { VisitorContext<?> ctx = (VisitorContext<?>) visitors.getWalkers().walkIterable(passthrough); assertTrue(ctx.canRemove()); ValueRecorder recorder = new ValueRecorder() { @Override public <T> boolean visitValue(T value, Codex<T> codex, VisitorContext<T> ctx) { ctx.remove(); return super.visitValue(value, codex, ctx); } }; List<Object> iterable = new ArrayList<Object>(Arrays.asList("a", "b")); ctx.walkIterable(passthrough).accept(recorder, iterable); assertEquals(Arrays.asList("a", "a", "b", "b"), recorder.values); assertTrue(iterable.isEmpty()); } @Test public void testListContext() { VisitorContext<?> ctx = (VisitorContext<?>) visitors.getWalkers().walkList(passthrough); assertTrue(ctx.canInsert()); assertTrue(ctx.canReplace()); assertTrue(ctx.canRemove()); ValueRecorder recorder = new ValueRecorder() { int count = 0; @Override public <T> boolean visitValue(T value, Codex<T> codex, VisitorContext<T> ctx) { switch (count) { case 0: assertEquals("a", value); ctx.insertBefore(codex.cast(String.valueOf(count))); break; case 1: assertEquals("b", value); ctx.insertAfter(codex.cast(String.valueOf(count))); break; case 2: assertEquals("c", value); ctx.remove(); break; case 3: assertEquals("d", value); ctx.replace(codex.cast(String.valueOf(count))); break; case 4: assertEquals("e", value); ctx.insertBefore(codex.cast(String.valueOf(count))); break; default: throw new RuntimeException(String.valueOf(count)); } count++; return super.visitValue(value, codex, ctx); } }; List<Object> list = new ArrayList<Object>(Arrays.asList("a", "b", "c", "d", "e")); ctx.walkList(passthrough).accept(recorder, list); assertEquals(Arrays.asList("0", "a", "b", "1", "3", "4", "e"), list); assertEquals(Arrays.asList("a", "a", "b", "b", "c", "c", "d", "d", "e", "e"), recorder.values); } @Test public void testListContextEmpty() { ValueRecorder recorder = new ValueRecorder() { @Override public <T> boolean visitValue(T value, Codex<T> codex, VisitorContext<T> ctx) { fail("Should not see this with an empty list"); return super.visitValue(value, codex, ctx); } }; visitors.getWalkers().walkList(passthrough).accept(recorder, Collections.emptyList()); assertTrue(recorder.values.isEmpty()); } @Test public void testListContextRemoveBefore() { ValueRecorder recorder = new ValueRecorder() { @Override public <T> boolean visitValue(T value, Codex<T> codex, VisitorContext<T> ctx) { assertEquals("Hello", value); ctx.remove(); ctx.insertBefore(codex.cast("World!")); return super.visitValue(value, codex, ctx); } }; List<Object> list = new ArrayList<Object>(Arrays.asList("Hello")); visitors.getWalkers().walkList(passthrough).accept(recorder, list); assertEquals(Collections.singletonList("World!"), list); assertEquals(Arrays.asList("Hello", "Hello"), recorder.values); } @Test public void testNullableContext() { NullableContext<Object> ctx = (NullableContext<Object>) visitors.getWalkers() .walkNullable(passthrough); assertTrue(ctx.canRemove()); assertTrue(ctx.canReplace()); assertNull(ctx.getValue(null)); Object value = new Object(); ctx.replace(value); assertTrue(ctx.didReplace()); assertSame(value, ctx.getValue(new Object())); ctx.remove(); assertNull(ctx.getValue(new Object())); // Verify that re-replacing the value will restore ctx.replace(value); assertSame(value, ctx.getValue(new Object())); ValueRecorder recorder = new ValueRecorder(); ctx.walkSingleton(passthrough).accept(recorder, value); assertEquals(Arrays.asList(value, value), recorder.values); } @Test public void testSingletonContext() { SingletonContext<Object> ctx = (SingletonContext<Object>) visitors.getWalkers() .walkSingleton(passthrough); assertTrue(ctx.canReplace()); assertNull(ctx.getValue(null)); Object value = new Object(); ctx.replace(value); assertTrue(ctx.didReplace()); assertSame(value, ctx.getValue(new Object())); ValueRecorder recorder = new ValueRecorder(); ctx.walkSingleton(passthrough).accept(recorder, value); assertEquals(Arrays.asList(value, value), recorder.values); } }