Skip to content

Commit

Permalink
Clarifying type conversion context hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
poetix committed Aug 9, 2012
1 parent 7c4f460 commit 9706f75
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 92 deletions.
23 changes: 14 additions & 9 deletions src/main/java/com/youdevise/variance/ImplicitTypeConversions.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@

public final class ImplicitTypeConversions {

public static final Supplier<TypeConversionContext> supplier = new Supplier<TypeConversionContext>() {
@Override public TypeConversionContext get() {
return current();
}
};
public static final TypeConversionContext implicitContext = new ProxyingTypeConversionContext(new Supplier<TypeConversionContext>() {
@Override public TypeConversionContext get() { return current(); }
});

private static final ThreadLocal<Stack<TypeConversionContext>> threadLocal =
new ThreadLocal<Stack<TypeConversionContext>>() {
@Override protected Stack<TypeConversionContext> initialValue() {
Stack<TypeConversionContext> contextStack = new Stack<TypeConversionContext>();
contextStack.push(TypeConversions.standardContext);
return contextStack;
}
};

public static TypeConversionContext current() {
return threadLocal.get().peek();
Stack<TypeConversionContext> stack = threadLocal.get();
if (stack.isEmpty()) {
return null;
}
return stack.peek();
}

public static void enterNew(TypeConversionContext context) {
Expand All @@ -31,12 +32,16 @@ public static void enterNew(TypeConversionContext context) {

public static void enterExtended(TypeConversionContext context) {
TypeConversionContext old = current();
enterNew(old.extendedWith(context));
if (old == null) {
enterNew(context);
} else {
enterNew(old.extendedWith(context));
}
}

public static void exit() {
Stack<TypeConversionContext> stack = threadLocal.get();
if (stack.size()==1) {
if (stack.size()==0) {
throw new IllegalStateException("Exit called without corresponding Enter");
}
stack.pop();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.youdevise.variance;

import com.google.common.base.Supplier;

public final class ProxyingTypeConversionContext implements TypeConversionContext {

private final Supplier<TypeConversionContext> supplier;

public ProxyingTypeConversionContext(Supplier<TypeConversionContext> supplier) {
this.supplier = supplier;
}

@Override
public TypeConversionContext extendedWith(TypeConversionContext ctx) {
return supplier.get().extendedWith(ctx);
}

@Override
public <C> C convert(Object o, Class<C> targetClass) {
return supplier.get().convert(o, targetClass);
}

@Override
public <C> boolean canConvert(Object o, Class<C> targetClass) {
if (supplier.get() == null) { return false; }
return supplier.get().canConvert(o, targetClass);
}
}
30 changes: 12 additions & 18 deletions src/main/java/com/youdevise/variance/Variant.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

Expand Down Expand Up @@ -60,30 +59,29 @@ private static Iterable<Variant> arrayToVariants(Object value) {
}

private final Object value;
private final Supplier<TypeConversionContext> typeConversionContextSupplier;
private final TypeConversionContext typeConversionContext;

private Variant(Object value) {
this(value, ImplicitTypeConversions.supplier);
this(value, new ChainedTypeConversionContext(ImplicitTypeConversions.implicitContext, TypeConversions.standardContext));
}


@SuppressWarnings("unchecked")
private Variant(Object value, Supplier<TypeConversionContext> typeConversionContextSupplier) {
private Variant(Object value, TypeConversionContext boundContext) {
if (value instanceof Iterable) {
this.value = bound((Iterable<Variant>) value, this);
this.value = bound((Iterable<Variant>) value, boundContext);
} else {
this.value = value;
}
this.typeConversionContextSupplier = typeConversionContextSupplier;
this.typeConversionContext = boundContext;
}

private Iterable<Variant> bound(Iterable<Variant> values, final Variant parent) {
Supplier<TypeConversionContext> supplier = new Supplier<TypeConversionContext>() {
@Override public TypeConversionContext get() {
return parent.context();
private Iterable<Variant> bound(Iterable<Variant> values, final TypeConversionContext typeConversionContext) {
return Iterables.transform(values, new Function<Variant, Variant>() {
@Override public Variant apply(Variant arg0) {
return new Variant(arg0.value, typeConversionContext);
}
};
return Iterables.transform(values, Variants.inContext(supplier));
});
}

public <C> C as(Class<C> targetClass) {
Expand All @@ -109,19 +107,15 @@ public <C> C[] asArrayOf(final Class<C> targetClass) {
}

public Variant in(TypeConversionContext ctx) {
return in(Suppliers.ofInstance(ctx));
}

public Variant in(Supplier<TypeConversionContext> ctx) {
return new Variant(value, ctx);
return new Variant(value, new ChainedTypeConversionContext(ctx, typeConversionContext));
}

public Class<?> valueClass() {
return value.getClass();
}

public TypeConversionContext context() {
return typeConversionContextSupplier.get();
return typeConversionContext;
}

public boolean isConvertibleTo(Class<?> targetClass) {
Expand Down
16 changes: 1 addition & 15 deletions src/main/java/com/youdevise/variance/Variants.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;

public final class Variants {

Expand All @@ -14,10 +12,6 @@ public final class Variants {
private Variants() { }

public static final Function<Object, Variant> toVariant(TypeConversionContext context) {
return Variants.toVariant(Suppliers.ofInstance(context));
}

public static final Function<Object, Variant> toVariant(Supplier<TypeConversionContext> context) {
return Functions.compose(Variants.inContext(context), toVariant);
}

Expand All @@ -31,15 +25,7 @@ public static <T> Function<Variant, T> variantTo(Class<T> targetClass, TypeConve
return Functions.compose(variantTo(targetClass), Variants.inContext(context));
}

public static <T> Function<Variant, T> variantTo(Class<T> targetClass, Supplier<TypeConversionContext> context) {
return Functions.compose(variantTo(targetClass), Variants.inContext(context));
}

public static Function<Variant, Variant> inContext(TypeConversionContext context) {
return Variants.inContext(Suppliers.ofInstance(context));
}

public static Function<Variant, Variant> inContext(final Supplier<TypeConversionContext> context) {
public static Function<Variant, Variant> inContext(final TypeConversionContext context) {
return new Function<Variant, Variant>() {
@Override public Variant apply(Variant variant) {
return variant.in(context);
Expand Down
91 changes: 41 additions & 50 deletions src/test/java/com/youdevise/variance/VariantTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,17 @@
import org.jmock.Mockery;
import org.junit.Test;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;

public class VariantTest {

private final Mockery context = new Mockery();

private static final class TestTypeConversionContext implements TypeConversionContext {
@Override
public <C> boolean canConvert(Object o, Class<C> targetClass) {
return TypeConversions.standardContext.canConvert(o, targetClass);
}

@Override
public <C> C convert(Object o, Class<C> targetClass) {
return TypeConversions.standardContext.convert(o, targetClass);
}

@Override
public TypeConversionContext extendedWith(TypeConversionContext ctx) {
return null;
}
}

@Test public void
stores_a_value() {
Variant variant = Variant.of("A string");
Expand Down Expand Up @@ -64,38 +47,43 @@ public TypeConversionContext extendedWith(TypeConversionContext ctx) {
}

@Test public void
is_bound_to_a_thread_local_type_conversion_context() {
Variant variant = Variant.of(1);

TypeConversionContext ctx = ImplicitTypeConversions.current();
is_bound_to_a_thread_local_type_conversion_context() {
ImplicitTypeConversions.enterExtended(contextWithMarker("marker"));

assertThat(variant.context(), sameInstance(ctx));
try {
assertThat(Variant.of(1).toString(), is("marker"));
} finally {
ImplicitTypeConversions.exit();
}
}

@Test public void
bound_type_conversion_context_changes_when_thread_local_context_changes() {
TypeConversionContext before = new TestTypeConversionContext();
ImplicitTypeConversions.enterNew(before);

Variant variant = Variant.of(1);

assertThat(variant.context(), sameInstance(before));

ImplicitTypeConversions.enterNew(contextWithMarker("before"));
assertThat(variant.toString(), is("before"));
ImplicitTypeConversions.exit();

TypeConversionContext after = new TestTypeConversionContext();
ImplicitTypeConversions.enterNew(after);
assertThat(variant.context(), sameInstance(after));
ImplicitTypeConversions.enterNew(contextWithMarker("after"));
assertThat(variant.toString(), is("after"));
ImplicitTypeConversions.exit();
}

@Test public void
can_be_bound_to_custom_context() {
TypeConversionContext newCtx = new TestTypeConversionContext();
Variant variant = Variant.of(1).in(contextWithMarker("custom"));

Variant variant = Variant.of(1).in(newCtx);
assertThat(variant.toString(), is("custom"));
}

@Test public void
custom_context_overrides_thread_local_context() {
Variant variant = Variant.of(1).in(contextWithMarker("custom"));

assertThat(variant.context(), sameInstance(newCtx));
ImplicitTypeConversions.enterNew(contextWithMarker("before"));
assertThat(variant.toString(), is("custom"));
ImplicitTypeConversions.exit();
}

@Test public void
Expand All @@ -115,14 +103,8 @@ public TypeConversionContext extendedWith(TypeConversionContext ctx) {
}

@Test(expected=IllegalArgumentException.class) public void
throws_illegal_argument_exception_if_context_cannot_convert_value() {
final TypeConversionContext mockCtx = context.mock(TypeConversionContext.class);

context.checking(new Expectations() {{
allowing(mockCtx).canConvert(12, String.class); will(returnValue(false));
}});

Variant.of(12).in(mockCtx).as(String.class);
throws_illegal_argument_exception_if_context_cannot_convert_value() {
Variant.of(12).as(Thread.class);
}

@Test public void
Expand Down Expand Up @@ -187,23 +169,32 @@ public TypeConversionContext extendedWith(TypeConversionContext ctx) {

@Test public void
iterable_members_are_bound_to_parent_context() {
TypeConversionContext childContext = new TestTypeConversionContext();
Variant child = Variant.of("1").in(childContext);
Variant child = Variant.of("1").in(contextWithMarker("child"));
Variant[] children = { child };

Variant parent = Variant.of(children);
assertThat(Iterables.getFirst(parent.asIterableOf(Variant.class), null).context(), Matchers.sameInstance(parent.context()));
assertThat(Iterables.getFirst(parent.asIterableOf(Variant.class), null).toString(), is("1"));
}

@Test public void
iterable_members_are_rebound_when_parent_is_transferred_to_new_context() {
TypeConversionContext childContext = new TestTypeConversionContext();
TypeConversionContext parentContext = new TestTypeConversionContext();
final Variant child = Variant.of("1").in(childContext);
final Variant child = Variant.of(1).in(contextWithMarker("child"));
Variant[] children = { child };

Variant parent = Variant.of(children);
Variant transferred = parent.in(parentContext);
assertThat(Iterables.getFirst(transferred.asIterableOf(Variant.class), null).context(), Matchers.sameInstance(parentContext));
Variant transferred = parent.in(contextWithMarker("parent"));
assertThat(Iterables.getFirst(transferred.asIterableOf(Variant.class), null).toString(), is("parent"));
}

private Function<Integer, String> contextMarker(final String marker) {
return new Function<Integer, String>() {
@Override public String apply(Integer arg0) { return marker; }
};
}

private TypeConversionContext contextWithMarker(String marker) {
return MatchingTypeConversionContext.builder()
.register(Integer.class, String.class, contextMarker(marker))
.build();
}
}

0 comments on commit 9706f75

Please sign in to comment.