Created
January 19, 2014 21:55
-
-
Save stephanenicolas/8511492 to your computer and use it in GitHub Desktop.
ViewListener that filters listened classes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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.annotation.Annotation; | |
import java.lang.ref.WeakReference; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Modifier; | |
import java.util.ArrayList; | |
import java.util.WeakHashMap; | |
import javax.inject.Singleton; | |
import roboguice.RoboGuice; | |
import roboguice.fragment.FragmentUtil; | |
import roboguice.fragment.FragmentUtil.f; | |
import com.google.inject.MembersInjector; | |
import com.google.inject.Provider; | |
import com.google.inject.TypeLiteral; | |
import com.google.inject.spi.TypeEncounter; | |
import com.google.inject.spi.TypeListener; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.view.View; | |
@Singleton | |
@SuppressWarnings({"unchecked","PMD"}) | |
public class ViewListener implements TypeListener { | |
@Override | |
public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) { | |
boolean isInRoboGuicePackage = false; | |
for (Class<?> c = typeLiteral.getRawType(); c!= Object.class; c = c.getSuperclass()) { | |
int indexDot = RoboGuice.class.getName().indexOf('.'); | |
if( typeLiteral.getRawType().getName().startsWith("android") ) { | |
System.out.println("Quitting 0"); | |
return; | |
} | |
if( c.getName().startsWith(RoboGuice.class.getName().substring(0,indexDot))) { | |
isInRoboGuicePackage = true; | |
} else if( isInRoboGuicePackage ) { | |
System.out.println("Quitting 1"); | |
break; | |
} | |
System.out.println(c.getName() +" injected "+isInRoboGuicePackage); | |
for (Field field : c.getDeclaredFields()) { | |
if (field.isAnnotationPresent(InjectView.class)) | |
if (Modifier.isStatic(field.getModifiers())) | |
throw new UnsupportedOperationException("Views may not be statically injected"); | |
else if (!View.class.isAssignableFrom(field.getType())) | |
throw new UnsupportedOperationException("You may only use @InjectView on fields descended from type View"); | |
else if (Context.class.isAssignableFrom(field.getDeclaringClass()) && !Activity.class.isAssignableFrom(field.getDeclaringClass())) | |
throw new UnsupportedOperationException("You may only use @InjectView in Activity contexts"); | |
else { | |
final f<?,?> utils = FragmentUtil.hasSupport | |
&& (FragmentUtil.supportActivity.isAssignableFrom(field.getDeclaringClass()) | |
|| FragmentUtil.supportFrag.fragmentType().isAssignableFrom(field.getDeclaringClass())) | |
? FragmentUtil.supportFrag : FragmentUtil.nativeFrag; | |
typeEncounter.register(new ViewMembersInjector<I>( | |
field, field.getAnnotation(InjectView.class), | |
typeEncounter, utils)); | |
} | |
else if (field.isAnnotationPresent(InjectFragment.class)) | |
if (!FragmentUtil.hasNative && !FragmentUtil.hasSupport) { | |
throw new RuntimeException(new ClassNotFoundException("No fragment classes were available")); | |
} else if (Modifier.isStatic(field.getModifiers())) { | |
throw new UnsupportedOperationException("Fragments may not be statically injected"); | |
} else { | |
final boolean assignableFromNative = FragmentUtil.hasNative && FragmentUtil.nativeFrag.fragmentType().isAssignableFrom(field.getType()); | |
final boolean assignableFromSupport = FragmentUtil.hasSupport && FragmentUtil.supportFrag.fragmentType().isAssignableFrom(field.getType()); | |
final boolean isSupportActivity = FragmentUtil.hasSupport && FragmentUtil.supportActivity.isAssignableFrom(field.getDeclaringClass()); | |
final boolean isNativeActivity = !isSupportActivity && Activity.class.isAssignableFrom(field.getDeclaringClass()); | |
if (isNativeActivity && assignableFromNative || isSupportActivity && assignableFromSupport) { | |
typeEncounter.register(new ViewMembersInjector<I>(field, field.getAnnotation(InjectFragment.class), typeEncounter, isNativeActivity ? FragmentUtil.nativeFrag:FragmentUtil.supportFrag)); | |
} else if (isNativeActivity && !assignableFromNative) { | |
// Error messages - these filters are comprehensive. The | |
// final else block will never execute. | |
throw new UnsupportedOperationException( | |
"You may only use @InjectFragment in native activities if fields are descended from type android.app.Fragment"); | |
} else if (!isSupportActivity && !isNativeActivity) { | |
throw new UnsupportedOperationException("You may only use @InjectFragment in Activity contexts"); | |
} else if (isSupportActivity && !assignableFromSupport) { | |
throw new UnsupportedOperationException( | |
"You may only use @InjectFragment in support activities if fields are descended from type android.support.v4.app.Fragment"); | |
} else { | |
throw new RuntimeException("This should never happen."); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* This class gets twice as many providers as necessary to do its job, look into optimizations in the future if this is a bottleneck | |
*/ | |
public static class ViewMembersInjector<T> implements MembersInjector<T> { | |
@edu.umd.cs.findbugs.annotations.SuppressWarnings("MS_SHOULD_BE_FINAL") | |
protected static WeakHashMap<Object,ArrayList<ViewMembersInjector<?>>> viewMembersInjectors = new WeakHashMap<Object, ArrayList<ViewMembersInjector<?>>>(); | |
protected Field field; | |
protected Annotation annotation; | |
protected WeakReference<T> instanceRef; | |
@SuppressWarnings("rawtypes") | |
protected FragmentUtil.f fragUtils; | |
@SuppressWarnings("rawtypes") | |
protected Provider fragManager; | |
protected Provider<Activity> activityProvider; | |
public ViewMembersInjector(Field field, Annotation annotation, TypeEncounter<T> typeEncounter, FragmentUtil.f<?,?> utils) { | |
this.field = field; | |
this.annotation = annotation; | |
this.activityProvider = typeEncounter.getProvider(Activity.class); | |
if( utils !=null ) { | |
this.fragUtils = utils; | |
this.fragManager = typeEncounter.getProvider(utils.fragmentManagerType()); | |
} | |
} | |
/** | |
* This is called when instance is injected by guice. Because the views may or may not be set up yet, | |
* we don't do the real view injection until later. | |
* | |
* @param instance the instance being injected by guice | |
*/ | |
public void injectMembers(T instance) { | |
synchronized (ViewMembersInjector.class) { | |
final Activity activity = activityProvider.get(); | |
final Object key = fragUtils != null && fragUtils.fragmentType().isInstance(instance) ? instance : activity; | |
if( key==null ) | |
return; | |
// Add a view injector for the key | |
ArrayList<ViewMembersInjector<?>> injectors = viewMembersInjectors.get(key); | |
if( injectors==null ) { | |
injectors = new ArrayList<ViewMembersInjector<?>>(); | |
viewMembersInjectors.put(key, injectors); | |
} | |
injectors.add(this); | |
this.instanceRef = new WeakReference<T>(instance); | |
} | |
} | |
public void reallyInjectMembers( Object activityOrFragment ) { | |
if( annotation instanceof InjectView ) | |
reallyInjectMemberViews(activityOrFragment); | |
else | |
reallyInjectMemberFragments(activityOrFragment); | |
} | |
/** | |
* This is when the view references are actually evaluated. | |
* | |
* I don't like all the hacks I had to put into this method. Instance is the object you're | |
* injecting into. ActivityOrFragment is the activity or fragment that you're injecting | |
* views into. Instance must equal activityOrFragment is activityOrFragment is a fragment, | |
* but they may differ if activityOrFragment is an activity. They should be allowed to differ | |
* so that you can inject views into arbitrary objects, but I don't know how to determine whether | |
* to get the view from the fragment or the activity for an arbitrary object, so I'm artificially | |
* limiting injection targets to the fragment itself for fragments. | |
* | |
* @param activityOrFragment an activity or fragment | |
*/ | |
protected void reallyInjectMemberViews(Object activityOrFragment) { | |
final T instance = fragUtils != null && fragUtils.fragmentType().isInstance(activityOrFragment) ? (T)activityOrFragment : instanceRef.get(); | |
if( instance==null ) | |
return; | |
if( activityOrFragment instanceof Context && !(activityOrFragment instanceof Activity )) | |
throw new UnsupportedOperationException("Can't inject view into a non-Activity context"); | |
View view = null; | |
try { | |
final InjectView injectView = (InjectView) annotation; | |
final int id = injectView.value(); | |
if( id>=0 ) | |
view = fragUtils != null && fragUtils.fragmentType().isInstance(activityOrFragment) ? fragUtils.getView(activityOrFragment).findViewById(id) : ((Activity)activityOrFragment).findViewById(id); | |
else | |
view = fragUtils !=null && fragUtils.fragmentType().isInstance(activityOrFragment) ? fragUtils.getView(activityOrFragment).findViewWithTag(injectView.tag()) : ((Activity)activityOrFragment).getWindow().getDecorView().findViewWithTag(injectView.tag()); | |
if (view == 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); | |
field.set(instance, view); | |
} 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", view != null ? view.getClass() : "(null)", view, | |
field.getType(), field.getName()), f); | |
} | |
} | |
/** | |
* This is when the view references are actually evaluated. | |
* @param activityOrFragment an activity or fragment | |
*/ | |
protected void reallyInjectMemberFragments(Object activityOrFragment) { | |
final T instance = instanceRef.get(); | |
if( instance==null ) | |
return; | |
if( activityOrFragment instanceof Context && !(activityOrFragment instanceof Activity )) | |
throw new UnsupportedOperationException("Can't inject fragment into a non-Activity context"); | |
Object fragment = null; | |
try { | |
final InjectFragment injectFragment = (InjectFragment) annotation; | |
final int id = injectFragment.value(); | |
if( id>=0 ) | |
fragment = fragUtils.findFragmentById(fragManager.get(), id); | |
else | |
fragment = fragUtils.findFragmentByTag(fragManager.get(),injectFragment.tag()); | |
if (fragment == 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); | |
field.set(instance, fragment); | |
} 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", fragment != null ? fragment.getClass() : "(null)", fragment, | |
field.getType(), field.getName()), f); | |
} | |
} | |
protected static void injectViews(Object activityOrFragment) { | |
synchronized ( ViewMembersInjector.class ) { | |
final ArrayList<ViewMembersInjector<?>> injectors = viewMembersInjectors.get(activityOrFragment); | |
if(injectors!=null) | |
for(ViewMembersInjector<?> viewMembersInjector : injectors) | |
viewMembersInjector.reallyInjectMembers(activityOrFragment); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment