/* * Copyright 2009 Michael Burton * * 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 roboguice.inject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.util.Map; import java.util.Set; import roboguice.RoboGuice; import com.google.inject.Binding; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.MembersInjector; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.HierarchyTraversalFilter; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import com.google.inject.util.Types; import android.app.Activity; import android.content.Context; import android.os.Bundle; /** * * @author Mike Burton * @author Pierre-Yves Ricau (py.ricau+roboguice@gmail.com) */ public class ExtrasListener implements TypeListener { protected Provider<Context> contextProvider; private HierarchyTraversalFilter filter; public ExtrasListener(Provider<Context> contextProvider) { this.contextProvider = contextProvider; } public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) { if( filter == null ) { filter = Guice.createHierarchyTraversalFilter(); } else { filter.reset(); } Class<?> c = typeLiteral.getRawType(); while( isWorthScanning(c)) { final Set<Field> allFields; allFields = filter.getAllFields(InjectExtra.class.getName(), c); if( allFields != null ) { for (Field field : allFields) { if (field.isAnnotationPresent(InjectExtra.class) ) if( Modifier.isStatic(field.getModifiers()) ) throw new UnsupportedOperationException("Extras may not be statically injected"); else typeEncounter.register(new ExtrasMembersInjector<I>(field, contextProvider, field.getAnnotation(InjectExtra.class))); } } c = c.getSuperclass(); } } private boolean isWorthScanning(Class<?> c) { return filter.isWorthScanningForFields(InjectExtra.class.getName(), c); } protected static class ExtrasMembersInjector<T> implements MembersInjector<T> { protected Field field; protected Provider<Context> contextProvider; protected InjectExtra annotation; public ExtrasMembersInjector(Field field, Provider<Context> contextProvider, InjectExtra annotation) { this.field = field; this.contextProvider = contextProvider; this.annotation = annotation; } public void injectMembers(T instance) { final Context context = contextProvider.get(); if( !(context instanceof Activity ) ) throw new UnsupportedOperationException(String.format("Extras may not be injected into contexts that are not Activities (error in class %s)",contextProvider.get().getClass().getSimpleName())); final Activity activity = (Activity) context; Object value; final String id = annotation.value(); final Bundle extras = activity.getIntent().getExtras(); if (extras == null || !extras.containsKey(id)) { // If no extra found and the extra injection is optional, no // injection happens. if (annotation.optional()) { return; } else { throw new IllegalStateException(String.format("Can't find the mandatory extra identified by key [%s] on field %s.%s", id, field .getDeclaringClass(), field.getName())); } } value = extras.get(id); value = convert(field, value, RoboGuice.getOrCreateBaseApplicationInjector(activity.getApplication())); /* * Please notice : null checking is done AFTER conversion. Having * * @Nullable on a field means "the injected value might be null", ie * "the converted value might be null". Which also means that if you * don't use @Nullable and a converter returns null, an exception will * be thrown (which I find to be the most logic behavior). */ if (value == null && Nullable.notNullable(field) ) { throw new NullPointerException(String.format("Can't inject null value into %s.%s when field is not @Nullable", field.getDeclaringClass(), field .getName())); } field.setAccessible(true); try { field.set(instance, value); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (IllegalArgumentException f) { throw new IllegalArgumentException(String.format("Can't assign %s value %s to %s field %s", value != null ? value.getClass() : "(null)", value, field.getType(), field.getName())); } } @SuppressWarnings({ "unchecked", "rawtypes" }) protected Object convert(Field field, Object value, Injector injector) { // Don't try to convert null or primitives if (value == null || field.getType().isPrimitive()) { return value; } // Building parameterized converter type // Please notice that the extra type and the field type must EXACTLY // match the declared converter parameter types. ParameterizedType pt = Types.newParameterizedType(ExtraConverter.class, value.getClass(), field.getType()); Key<?> key = Key.get(pt); // Getting bindings map to check if a binding exists // We DO NOT currently check for injector's parent bindings. Should we ? Map<Key<?>, Binding<?>> bindings = injector.getBindings(); if (bindings.containsKey(key)) { ExtraConverter converter = (ExtraConverter) injector.getInstance(key); value = converter.convert(value); } return value; } } }