mirror of https://github.com/apache/jclouds.git
introduced functional model for dynamic proxies
This commit is contained in:
parent
c42f59a8da
commit
bd9e998b12
|
@ -0,0 +1,142 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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.jclouds.reflect;
|
||||||
|
|
||||||
|
import static com.google.common.base.Objects.equal;
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Predicates.notNull;
|
||||||
|
import static com.google.common.base.Throwables.propagate;
|
||||||
|
import static com.google.common.collect.Iterables.all;
|
||||||
|
import static org.jclouds.util.Throwables2.propagateIfPossible;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jclouds.reflect.Invocation.Result;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.reflect.Invokable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static utilities relating to functional Java reflection.
|
||||||
|
*
|
||||||
|
* @since 1.6
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public final class FunctionalReflection {
|
||||||
|
/**
|
||||||
|
* Returns a proxy instance that implements {@code interfaceType} by dispatching method invocations to
|
||||||
|
* {@code invocationFunction}. The class loader of {@code interfaceType} will be used to define the proxy class.
|
||||||
|
* <p>
|
||||||
|
* Usage example:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* httpAdapter = new Function<Invocation, Result>() {
|
||||||
|
* public Result apply(Invocation in) {
|
||||||
|
* try {
|
||||||
|
* HttpRequest request = parseRequest(in);
|
||||||
|
* HttpResponse response = invoke(request);
|
||||||
|
* return Result.success(parseJson(response));
|
||||||
|
* } catch (Exception e) {
|
||||||
|
* return Result.failure(e);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* client = FunctionalReflection.newProxy(Client.class, httpAdapter);
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param invocationFunction
|
||||||
|
* returns a result or a top-level exception, or result
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if {@code interfaceType} does not specify the type of a Java interface
|
||||||
|
* @see com.google.common.reflect.AbstractInvocationHandler#invoke(Object, Method, Object[])
|
||||||
|
* @see com.google.common.reflect.Reflection#newProxy(Class, java.lang.reflect.InvocationHandler)
|
||||||
|
*/
|
||||||
|
public static <T> T newProxy(Class<T> interfaceType, Function<Invocation, Result> invocationFunction) {
|
||||||
|
checkNotNull(interfaceType, "interfaceType");
|
||||||
|
checkNotNull(invocationFunction, "invocationFunction");
|
||||||
|
checkArgument(interfaceType.isInterface(), "%s is not an interface", interfaceType);
|
||||||
|
Object object = Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class<?>[] { interfaceType },
|
||||||
|
new FunctionalInvocationHandler<T>(interfaceType, invocationFunction));
|
||||||
|
return interfaceType.cast(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class FunctionalInvocationHandler<T> extends
|
||||||
|
com.google.common.reflect.AbstractInvocationHandler {
|
||||||
|
private final Class<T> interfaceType;
|
||||||
|
private final Function<Invocation, Result> invocationFunction;
|
||||||
|
|
||||||
|
private FunctionalInvocationHandler(Class<T> interfaceType, Function<Invocation, Result> invocationFunction) {
|
||||||
|
this.interfaceType = interfaceType;
|
||||||
|
this.invocationFunction = invocationFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object handleInvocation(Object proxy, Method invoked, Object[] argv) throws Throwable {
|
||||||
|
List<Object> args = Arrays.asList(argv);
|
||||||
|
if (all(args, notNull()))
|
||||||
|
args = ImmutableList.copyOf(args);
|
||||||
|
else
|
||||||
|
args = Collections.unmodifiableList(args);
|
||||||
|
Invokable<?, ?> invokable = Invokable.class.cast(Invokable.from(invoked));
|
||||||
|
// not yet support the proxy arg
|
||||||
|
Invocation invocation = Invocation.create(interfaceType, invokable, args);
|
||||||
|
Result result;
|
||||||
|
try {
|
||||||
|
result = invocationFunction.apply(invocation);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
result = Result.fail(e);
|
||||||
|
}
|
||||||
|
if (result.getThrowable().isPresent()) {
|
||||||
|
propagateIfPossible(result.getThrowable().get(), invocation.getInvokable().getExceptionTypes());
|
||||||
|
throw propagate(result.getThrowable().get());
|
||||||
|
}
|
||||||
|
return result.getResult().orNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
FunctionalInvocationHandler<?> that = FunctionalInvocationHandler.class.cast(o);
|
||||||
|
return equal(this.interfaceType, that.interfaceType)
|
||||||
|
&& equal(this.invocationFunction, that.invocationFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(interfaceType, invocationFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return invocationFunction.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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.jclouds.reflect;
|
||||||
|
|
||||||
|
import static com.google.common.base.Objects.equal;
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jclouds.javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Objects.ToStringHelper;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.reflect.Invokable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context needed to call {@link com.google.common.reflect.Invokable#invoke(Object, Object...)}
|
||||||
|
*
|
||||||
|
* @author Adrian Cole
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public final class Invocation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this class when the invokable could be inherited. For example, a method is inherited when it cannot be
|
||||||
|
* retrieved by {@link Class#getDeclaredMethods()}, but it can be retrieved by {@link Class#getMethods()}.
|
||||||
|
*
|
||||||
|
* @param interfaceType
|
||||||
|
* type that either declared or inherited {@code invokable}, or was forwarded a call to it.
|
||||||
|
* @param args
|
||||||
|
* as these represent parameters, can contain nulls
|
||||||
|
*/
|
||||||
|
public static Invocation create(Class<?> interfaceType, Invokable<?, ?> invokable, List<Object> args) {
|
||||||
|
checkArgument(invokable.getDeclaringClass().isAssignableFrom(interfaceType), "%s isn't assignable from %s",
|
||||||
|
invokable.getDeclaringClass(), interfaceType);
|
||||||
|
return new Invocation(interfaceType, invokable, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: use {@link #create(Class, Invokable, List)} when the invokable was inherited.
|
||||||
|
*
|
||||||
|
* @param args
|
||||||
|
* as these represent parameters, can contain nulls
|
||||||
|
*/
|
||||||
|
public static Invocation create(Invokable<?, ?> invokable, List<Object> args) {
|
||||||
|
return new Invocation(invokable.getDeclaringClass(), invokable, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Class<?> interfaceType;
|
||||||
|
private final Invokable<?, ?> invokable;
|
||||||
|
private final List<Object> args;
|
||||||
|
|
||||||
|
private Invocation(Class<?> interfaceType, Invokable<?, ?> invokable, List<Object> args) {
|
||||||
|
this.interfaceType = checkNotNull(interfaceType, "interfaceType");
|
||||||
|
this.invokable = checkNotNull(invokable, "invokable");
|
||||||
|
this.args = checkNotNull(args, "args");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* different than {@link Invokable#getDeclaringClass()} when {@link #getInvokable()} is a member of a class it was
|
||||||
|
* not declared in.
|
||||||
|
*/
|
||||||
|
public Class<?> getInterfaceType() {
|
||||||
|
return interfaceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* what we can invoke
|
||||||
|
*/
|
||||||
|
public Invokable<?, ?> getInvokable() {
|
||||||
|
return invokable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* arguments applied to {@link #getInvokable()} during {@link Invokable#invoke(Object, Object...)}
|
||||||
|
*
|
||||||
|
* @param args
|
||||||
|
* as these represent parameters, can contain nulls
|
||||||
|
*/
|
||||||
|
public List<Object> getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
Invocation that = Invocation.class.cast(o);
|
||||||
|
return equal(this.interfaceType, that.interfaceType) && equal(this.invokable, that.invokable)
|
||||||
|
&& equal(this.args, that.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(interfaceType, invokable, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Objects.toStringHelper("").omitNullValues().add("interfaceType", interfaceType)
|
||||||
|
.add("invokable", invokable).add("args", args.size() != 0 ? args : null).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result of an invocation which is either successful or failed, but not both.
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public final static class Result {
|
||||||
|
public static Result success(@Nullable Object result) {
|
||||||
|
return new Result(Optional.fromNullable(result), Optional.<Throwable> absent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result fail(Throwable throwable) {
|
||||||
|
return new Result(Optional.absent(), Optional.of(throwable));
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Optional<Object> result;
|
||||||
|
private final Optional<Throwable> throwable;
|
||||||
|
|
||||||
|
private Result(Optional<Object> result, Optional<Throwable> throwable) {
|
||||||
|
this.result = checkNotNull(result, "result");
|
||||||
|
this.throwable = checkNotNull(throwable, "throwable");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result of{@link Invokable#invoke(Object, Object...)}
|
||||||
|
*/
|
||||||
|
public Optional<Object> getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* throwable received during {@link Invokable#invoke(Object, Object...)}
|
||||||
|
*/
|
||||||
|
public Optional<Throwable> getThrowable() {
|
||||||
|
return throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
Result that = Result.class.cast(o);
|
||||||
|
return equal(this.result.orNull(), that.result.orNull())
|
||||||
|
&& equal(this.throwable.orNull(), that.throwable.orNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(result.orNull(), throwable.orNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return string().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ToStringHelper string() {
|
||||||
|
return Objects.toStringHelper("").omitNullValues().add("result", result.orNull())
|
||||||
|
.add("throwable", throwable.orNull());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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.jclouds.reflect;
|
||||||
|
|
||||||
|
import static com.google.common.base.Objects.equal;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import org.jclouds.javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.annotations.Beta;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Objects.ToStringHelper;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the context of a successful call to {@link com.google.common.reflect.Invokable#invoke(Object, Object...)}
|
||||||
|
*
|
||||||
|
* @author Adrian Cole
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public final class InvocationSuccess {
|
||||||
|
public static InvocationSuccess create(Invocation invocation, @Nullable Object result) {
|
||||||
|
return new InvocationSuccess(invocation, Optional.fromNullable(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Invocation invocation;
|
||||||
|
private final Optional<Object> result;
|
||||||
|
|
||||||
|
private InvocationSuccess(Invocation invocation, Optional<Object> result) {
|
||||||
|
this.invocation = checkNotNull(invocation, "invocation");
|
||||||
|
this.result = checkNotNull(result, "result");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* what was invocation
|
||||||
|
*/
|
||||||
|
public Invocation getInvocation() {
|
||||||
|
return invocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Object> getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
InvocationSuccess that = InvocationSuccess.class.cast(o);
|
||||||
|
return equal(this.invocation, that.invocation) && equal(this.result.orNull(), that.result.orNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(invocation, result.orNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return string().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ToStringHelper string() {
|
||||||
|
return Objects.toStringHelper("").omitNullValues().add("invocation", invocation).add("result", result.orNull());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds licenses this file
|
||||||
|
* to you 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.jclouds.reflect;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotEquals;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.Assert.assertNull;
|
||||||
|
import static org.testng.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.jclouds.reflect.Invocation.Result;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Adrian Cole
|
||||||
|
*/
|
||||||
|
@Test(singleThreaded = true)
|
||||||
|
public class FunctionalReflectionTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a method only has reference to its declaring type, not the interface specified to the proxy. this shows how to get
|
||||||
|
* access to the actual proxied interface
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void testCanAccessInterfaceTypeInsideFunction() {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
assertEquals(e.getInvokable().getDeclaringClass(), Set.class);
|
||||||
|
assertEquals(e.getInterfaceType(), SortedSet.class);
|
||||||
|
return Result.success(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FunctionalReflection.newProxy(SortedSet.class, test).add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test(expectedExceptions = UnsupportedOperationException.class)
|
||||||
|
public void testNullArgsAreAllowedAndUnmodifiable() {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
assertNotNull(e.getArgs());
|
||||||
|
assertNull(e.getArgs().get(0));
|
||||||
|
e.getArgs().add("foo");
|
||||||
|
throw new AssertionError("shouldn't be able to mutate the list!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FunctionalReflection.newProxy(Set.class, test).add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Test(expectedExceptions = UnsupportedOperationException.class)
|
||||||
|
public void testImmutableListWhenArgsAreNotNull() {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
assertNotNull(e.getArgs());
|
||||||
|
assertTrue(e.getArgs() instanceof ImmutableList);
|
||||||
|
assertEquals(e.getArgs().get(0), "foo");
|
||||||
|
e.getArgs().add("bar");
|
||||||
|
throw new AssertionError("shouldn't be able to mutate the list!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FunctionalReflection.newProxy(Set.class, test).add("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expectedExceptions = IOException.class, expectedExceptionsMessageRegExp = "io")
|
||||||
|
public void testPropagatesDeclaredException() throws IOException {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
return Result.fail(new IOException("io"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Closeable closeable = FunctionalReflection.newProxy(Closeable.class, test);
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* for example, someone could have enabled assertions, or there could be a recoverable ServiceConfigurationError
|
||||||
|
*/
|
||||||
|
@Test(expectedExceptions = AssertionError.class, expectedExceptionsMessageRegExp = "assert")
|
||||||
|
public void testPropagatesError() throws IOException {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
return Result.fail(new AssertionError("assert"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Closeable closeable = FunctionalReflection.newProxy(Closeable.class, test);
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: coerce things like this to UncheckedTimeoutException and friends
|
||||||
|
@Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*timeout")
|
||||||
|
public void testWrapsDeclaredException() throws IOException {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
return Result.fail(new TimeoutException("timeout"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Closeable closeable = FunctionalReflection.newProxy(Closeable.class, test);
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testToStringEqualsFunction() {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
return Result.success("foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "bar";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Closeable closeable = FunctionalReflection.newProxy(Closeable.class, test);
|
||||||
|
assertEquals(closeable.toString(), "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHashCodeDifferentiatesOnInterface() {
|
||||||
|
final Function<Invocation, Result> test = new Function<Invocation, Result>() {
|
||||||
|
public Result apply(Invocation e) {
|
||||||
|
return Result.success(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
return 1111;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Appendable appendable1 = FunctionalReflection.newProxy(Appendable.class, test);
|
||||||
|
Appendable appendable2 = FunctionalReflection.newProxy(Appendable.class, test);
|
||||||
|
assertEquals(appendable1.hashCode(), appendable2.hashCode());
|
||||||
|
|
||||||
|
Closeable closeable = FunctionalReflection.newProxy(Closeable.class, test);
|
||||||
|
assertNotEquals(appendable1.hashCode(), closeable.hashCode());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue