/*
* Copyright (C) 2009 Google 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.
*/
package com.google.guiceberry.controllable;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import java.util.Collection;
import java.util.Map;
/**
* As documented at length in the <a
* href="http://www.google.com/codesearch/p?hl=en#yL0uRk1mhCY/trunk/doc/tutorial/test/testng/tutorial_1_server/Example4InjectionControllerTest.java">
* Example4InjectionControllerTest.java tutorial</a> and others,
* Controllable Injections need to be set up through an {@link IcMaster},
* which is to participate both in the building of the test Injector as well as
* the server Injector.
*
* <p>Both Injectors need to basically agree on two different things: the list
* of classes/keys that are subject to being controlled and, for each of these
* classes/keys the {@link IcStrategy} that is to be used.
*
* <p>Then, the client module (built through {@link #buildClientModule()} is to
* be added to the test Injector's list of modules; and, on the server side, the
* {@link #buildServerModule(Collection)} is to be used.
*
* @author Luiz-Otavio Zorzella
* @author Jesse Wilson
*/
public final class IcMaster {
private final Map<Key<?>, IcStrategy> controlledKeyToStrategyMap =
Maps.newHashMap();
/**
* Convenient wrapper to {@link #thatControls(IcStrategy, Key...)} for
* un-annotated {@code classes}.
*
* <p>Note that these are totally equivalent statements:
*
* <pre>
* thatControls(someStrategy(),
* MyClass.class)
* </pre>
*
* and
*
* <pre>
* thatControls(someStrategy(),
* Key.get(MyClass.class))
* </pre>
*
*
* @return itself, for method chaining
*/
public IcMaster thatControls(IcStrategy strategy,
Class<?>... classes) {
for (Class<?> clazz : classes) {
Key<?> key = Key.get(clazz);
if (controlledKeyToStrategyMap.containsKey(key)) {
throw new IllegalArgumentException(String.format(
"The Key '%s' has already been declared as controlled. " +
"Remove the duplicate declaration.", key));
}
controlledKeyToStrategyMap.put(key, strategy);
}
return this;
}
/**
* Declares the given annotated classes (through their {@code keys}) as being
* subject to Controllable Injection with the given {@code strategy}.
*
* <p>For example, to control the statement
* {@code @Inject @MyAnnotation MyClass myClass;} use the following:
*
* <pre>
* IcMaster icMaster = new IcMaster()
* .thatControls(someStrategy(),
* Key.get(MyClass.class, MyAnnotation.class));
* </pre>
*
* <p>Passing the same key twice to the same {@link IcMaster} results in an
* {@link IllegalArgumentException}.
*
* <p>To control generified classes, use the
* {@link com.google.inject.TypeLiteral} anonymous inner class trick.
* E.g. to control {@code @Inject MyClass<SomeType> myClass}}, use:
*
* <pre>
* IcMaster icMaster = new IcMaster()
* .thatControls(someStrategy(),
* Key.get(new TypeLiteral<MyClass<SomeType>>(){}));
* </pre>
*
* @return itself, for method chaining
*/
public IcMaster thatControls(IcStrategy strategy,
Key<?>... keys) {
for (Key<?> key : keys) {
if (controlledKeyToStrategyMap.containsKey(key)) {
throw new IllegalArgumentException(String.format(
"The Key '%s' has already been declared as controlled. " +
"Remove the duplicate declaration.", key));
}
controlledKeyToStrategyMap.put(key, strategy);
}
return this;
}
/**
* Use the {@link Module} returned from this method when constructing your
* test {@link Injector}.
*/
public Module buildClientModule() {
return new ControllableInjectionClientModule(controlledKeyToStrategyMap);
}
/**
* Use the {@link Module} returned from this method <em>instead</em> of the
* {@code modules} passes as argument to create your server {@link Injector}
* so it honors the declared Controllable Injections.
*
* <p>The returned {@link Module} is equivalent to the given {@code modules},
* except that the bindings to the classes/keys declared to be subject to
* be controlled (through {@link #thatControls(IcStrategy, Class...)}) will
* be rewritten to honor this.
*/
public Module buildServerModule(final Module... modules) {
return new InterceptingBindingsBuilder()
.install(modules)
.install(new ProvisionInterceptorModule())
.install(new ControllableInjectionServerModule(controlledKeyToStrategyMap))
.intercept(controlledKeyToStrategyMap.keySet())
.build();
}
/**
* @see #buildServerModule(Module...)
*/
public Module buildServerModule(final Collection<? extends Module> modules) {
return new InterceptingBindingsBuilder()
.install(modules)
.install(new ProvisionInterceptorModule())
.install(new ControllableInjectionServerModule(controlledKeyToStrategyMap))
.intercept(controlledKeyToStrategyMap.keySet())
.build();
}
private static class ProvisionInterceptorModule extends AbstractModule {
@Override
protected void configure() {
bind(ProvisionInterceptor.class).to(MyProvisionInterceptor.class);
}
private static class MyProvisionInterceptor implements ProvisionInterceptor {
@Inject Injector injector;
@SuppressWarnings("unchecked")
public <T> T intercept(Key<T> key, Provider<? extends T> delegate) {
IcServer<T> instance = (IcServer<T>) injector.getInstance(IcStrategy.wrap(IcServer.class, key));
return instance.getOverride(delegate);
}
}
}
}