/** * Copyright (C) 2008 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.inject.internal; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.inject.Binder; import com.google.inject.Exposed; import com.google.inject.Key; import com.google.inject.PrivateBinder; import com.google.inject.Provider; import com.google.inject.internal.BytecodeGen.Visibility; import com.google.inject.internal.util.StackTraceElements; import com.google.inject.spi.BindingTargetVisitor; import com.google.inject.spi.Dependency; import com.google.inject.spi.HasDependencies; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderWithExtensionVisitor; import com.google.inject.spi.ProvidesMethodBinding; import com.google.inject.spi.ProvidesMethodTargetVisitor; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; import java.util.Set; /** * A provider that invokes a method and returns its result. * * @author jessewilson@google.com (Jesse Wilson) */ public abstract class ProviderMethod<T> implements ProviderWithExtensionVisitor<T>, HasDependencies, ProvidesMethodBinding<T> { /** * Creates a {@link ProviderMethod}. * * <p>Unless {@code skipFastClassGeneration} is set, this will use {@link FastClass} to invoke * the actual method, since it is significantly faster. However, this will fail if the method is * {@code private} or {@code protected}, since fastclass is subject to java access policies. */ static <T> ProviderMethod<T> create(Key<T> key, Method method, Object instance, ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders, Class<? extends Annotation> scopeAnnotation, boolean skipFastClassGeneration) { int modifiers = method.getModifiers(); /*if[AOP]*/ if (!skipFastClassGeneration && !Modifier.isPrivate(modifiers) && !Modifier.isProtected(modifiers)) { try { // We use an index instead of FastMethod to save a stack frame. return new FastClassProviderMethod<T>( key, method, instance, dependencies, parameterProviders, scopeAnnotation); } catch (net.sf.cglib.core.CodeGenerationException e) {/* fall-through */} } /*end[AOP]*/ if (!Modifier.isPublic(modifiers) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) { method.setAccessible(true); } return new ReflectionProviderMethod<T>( key, method, instance, dependencies, parameterProviders, scopeAnnotation); } protected final Object instance; protected final Method method; private final Key<T> key; private final Class<? extends Annotation> scopeAnnotation; private final ImmutableSet<Dependency<?>> dependencies; private final List<Provider<?>> parameterProviders; private final boolean exposed; /** * @param method the method to invoke. It's return type must be the same type as {@code key}. */ private ProviderMethod(Key<T> key, Method method, Object instance, ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders, Class<? extends Annotation> scopeAnnotation) { this.key = key; this.scopeAnnotation = scopeAnnotation; this.instance = instance; this.dependencies = dependencies; this.method = method; this.parameterProviders = parameterProviders; this.exposed = method.isAnnotationPresent(Exposed.class); } public Key<T> getKey() { return key; } public Method getMethod() { return method; } // exposed for GIN public Object getInstance() { return instance; } public Object getEnclosingInstance() { return instance; } public void configure(Binder binder) { binder = binder.withSource(method); if (scopeAnnotation != null) { binder.bind(key).toProvider(this).in(scopeAnnotation); } else { binder.bind(key).toProvider(this); } if (exposed) { // the cast is safe 'cause the only binder we have implements PrivateBinder. If there's a // misplaced @Exposed, calling this will add an error to the binder's error queue ((PrivateBinder) binder).expose(key); } } public T get() { Object[] parameters = new Object[parameterProviders.size()]; for (int i = 0; i < parameters.length; i++) { parameters[i] = parameterProviders.get(i).get(); } try { @SuppressWarnings({ "unchecked", "UnnecessaryLocalVariable" }) T result = (T) doProvision(parameters); return result; } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InvocationTargetException e) { throw Exceptions.rethrowCause(e); } } /** Extension point for our subclasses to implement the provisioning strategy. */ abstract Object doProvision(Object[] parameters) throws IllegalAccessException, InvocationTargetException; public Set<Dependency<?>> getDependencies() { return dependencies; } @SuppressWarnings("unchecked") public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding) { if (visitor instanceof ProvidesMethodTargetVisitor) { return ((ProvidesMethodTargetVisitor<T, V>)visitor).visit(this); } return visitor.visit(binding); } @Override public String toString() { return "@Provides " + StackTraceElements.forMember(method); } @Override public boolean equals(Object obj) { if (obj instanceof ProviderMethod) { ProviderMethod o = (ProviderMethod)obj; return method.equals(o.method) && instance.equals(o.instance); } else { return false; } } @Override public int hashCode() { // Avoid calling hashCode on 'instance', which is a user-object // that might not be expecting it. // (We need to call equals, so we do. But we can avoid hashCode.) return Objects.hashCode(method); } /*if[AOP]*/ /** * A {@link ProviderMethod} implementation that uses {@link net.sf.cglib.reflect.FastClass#invoke} * to invoke the provider method. */ private static final class FastClassProviderMethod<T> extends ProviderMethod<T> { final net.sf.cglib.reflect.FastClass fastClass; final int methodIndex; FastClassProviderMethod(Key<T> key, Method method, Object instance, ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders, Class<? extends Annotation> scopeAnnotation) { super(key, method, instance, dependencies, parameterProviders, scopeAnnotation); // We need to generate a FastClass for the method's class, not the object's class. this.fastClass = BytecodeGen.newFastClass(method.getDeclaringClass(), Visibility.forMember(method)); // Use the Signature overload of getIndex because it properly uses return types to identify // particular methods. This is normally irrelevant, except in the case of covariant overrides // which java implements with a compiler generated bridge method to implement the override. this.methodIndex = fastClass.getIndex( new net.sf.cglib.core.Signature( method.getName(), org.objectweb.asm.Type.getMethodDescriptor(method))); Preconditions.checkArgument(this.methodIndex >= 0, "Could not find method %s in fast class for class %s", method, method.getDeclaringClass()); } @Override public Object doProvision(Object[] parameters) throws IllegalAccessException, InvocationTargetException { return fastClass.invoke(methodIndex, instance, parameters); } } /*end[AOP]*/ /** * A {@link ProviderMethod} implementation that invokes the method using normal java reflection. */ private static final class ReflectionProviderMethod<T> extends ProviderMethod<T> { ReflectionProviderMethod(Key<T> key, Method method, Object instance, ImmutableSet<Dependency<?>> dependencies, List<Provider<?>> parameterProviders, Class<? extends Annotation> scopeAnnotation) { super(key, method, instance, dependencies, parameterProviders, scopeAnnotation); } @Override Object doProvision(Object[] parameters) throws IllegalAccessException, InvocationTargetException { return method.invoke(instance, parameters); } } }