package org.opensilk.common.ui.mortar; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import org.opensilk.common.core.mortar.DaggerService; import org.opensilk.common.core.util.ObjectUtils; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.LinkedHashMap; import java.util.Map; import dagger.Component; import mortar.MortarScope; import static java.lang.String.format; import static org.opensilk.common.core.mortar.DaggerService.DAGGER_SERVICE; /** * Creates {@link MortarScope}s for screens that may be annotated with {@link WithComponentFactory}, * {@link WithComponent}. * * Taken from mortar sample, modified for dagger2 the {@link WithComponent} annotation * requires only one {@link dagger.Component#modules() module} defined with either a default * constuctor or single arg constructor that takes the containing {@link flow.Path screen} * the component dependencies are assumed to be a single component that can be fetched with * {@link mortar.dagger2support.DaggerService#getDaggerComponent(android.content.Context)} * from the parent context. * * TODO remove @WithComponent, @WithComponentFactory is more flexible and uses way less reflection. */ public class ScreenScoper { public static final String SERVICE_NAME = ScreenScoper.class.getName(); private static final ComponentFactory NO_FACTORY = new ComponentFactory() { @Override protected Object createDaggerComponent(Resources resources, MortarScope parentScope, Object screen) { throw new UnsupportedOperationException(); } }; private final Map<Class, ComponentFactory> moduleFactoryCache = new LinkedHashMap<>(); @SuppressLint("WrongConstant") public static ScreenScoper getService(Context context) { //noinspection ResourceType return (ScreenScoper) context.getSystemService(SERVICE_NAME); } public static ScreenScoper getService(MortarScope scope) { if (scope.hasService(SERVICE_NAME)) { return scope.getService(SERVICE_NAME); } throw new IllegalArgumentException(String.format("No ScreenScoper service in scope %s", scope.getName())); } public MortarScope getScreenScope(Context context, Screen screen, Object... services) { MortarScope parentScope = MortarScope.getScope(context); return getScreenScope(context.getResources(), parentScope, screen, services); } /** * Finds or creates the scope for the given screen, honoring its optional {@link * WithComponentFactory} or {@link WithComponent} annotation. Note that scopes are also created * for unannotated screens. */ public MortarScope getScreenScope(Resources resources, MortarScope parentScope, Screen screen, Object... services) { MortarScope childScope = parentScope.findChild(screen.getName()); if (childScope == null) { ComponentFactory componentFactory = getModuleFactory(screen); Object childComponent; if (componentFactory != NO_FACTORY) { childComponent = componentFactory.createDaggerComponent(resources, parentScope, screen); } else { throw new IllegalArgumentException("Screen must have a component"); } MortarScope.Builder bob = parentScope.buildChild() .withService(DAGGER_SERVICE, childComponent); if (services != null && services.length > 0) { if (services.length % 2 != 0) { throw new IllegalArgumentException("Services must be name, service pairs"); } for (int ii=0; ii<services.length; ii+=2) { bob.withService((String) services[ii], services[ii+1]); } } childScope = bob.build(screen.getName()); } return childScope; } private ComponentFactory getModuleFactory(Object screen) { Class<?> screenType = ObjectUtils.getClass(screen); ComponentFactory componentFactory = moduleFactoryCache.get(screenType); if (componentFactory != null) return componentFactory; WithComponent withComponent = screenType.getAnnotation(WithComponent.class); if (withComponent != null) { //component class Class<?> componentClass = withComponent.value(); Component component = componentClass.getAnnotation(Component.class); // get modules Class<?>[] modules = component.modules(); //only want to deal with on module at the moment if (modules.length != 1) { throw new IllegalArgumentException( format("Component %s for screen %s can only have one module." + "Use the factory or fix this if you need more than one", componentClass.getName(), screen)); } Class<?> moduleClass = modules[0]; Constructor<?>[] constructors = moduleClass.getDeclaredConstructors(); if (constructors.length != 1) { throw new IllegalArgumentException( format("Module %s for screen %s should have exactly one public constructor", moduleClass.getName(), screen)); } Constructor moduleConstructor = constructors[0]; Class[] parameters = moduleConstructor.getParameterTypes(); if (parameters.length > 1) { throw new IllegalArgumentException( format("Module %s for screen %s should have 0 or 1 parameter", moduleClass.getName(), screen)); } Class screenParameter; if (parameters.length == 1) { screenParameter = parameters[0]; if (!screenParameter.isInstance(screen)) { throw new IllegalArgumentException(format("Module %s for screen %s should have a " + "constructor parameter that is a super class of %s", moduleClass.getName(), screen, screen.getClass().getName())); } } else { screenParameter = null; } try { if (screenParameter == null) { componentFactory = new NoArgsFactory(componentClass, moduleConstructor); } else { componentFactory = new SingleArgFactory(componentClass, moduleConstructor); } } catch (Exception e) { throw new RuntimeException( format("Failed to instantiate module %s for screen %s", moduleClass.getName(), screen), e); } } if (componentFactory == null) { WithComponentFactory withComponentFactory = screenType.getAnnotation(WithComponentFactory.class); if (withComponentFactory != null) { Class<? extends ComponentFactory> mfClass = withComponentFactory.value(); try { componentFactory = mfClass.newInstance(); } catch (Exception e) { throw new RuntimeException(format("Failed to instantiate module factory %s for screen %s", withComponentFactory.value().getName(), screen), e); } } } if (componentFactory == null) componentFactory = NO_FACTORY; moduleFactoryCache.put(screenType, componentFactory); return componentFactory; } private static class NoArgsFactory extends ComponentFactory<Object> { final Class component; final Constructor moduleConstructor; private NoArgsFactory(Class component, Constructor moduleConstructor) { this.component = component; this.moduleConstructor = moduleConstructor; } @Override protected Object createDaggerComponent(Resources resources, MortarScope parentScope, Object ignored) { Object parentComponent = parentScope.getService(DAGGER_SERVICE); if (parentComponent != null) { return DaggerService.createComponent(component, parentComponent); } else { return DaggerService.createComponent(component); } } } private static class SingleArgFactory extends ComponentFactory { final Class component; final Constructor moduleConstructor; public SingleArgFactory(Class component, Constructor moduleConstructor) { this.component = component; this.moduleConstructor = moduleConstructor; } @Override protected Object createDaggerComponent(Resources resources, MortarScope parentScope, Object screen) { try { Object module = moduleConstructor.newInstance(screen); Object parentComponent = parentScope.getService(DAGGER_SERVICE); if (parentComponent != null) { return DaggerService.createComponent(component, module, parentComponent); } else { return DaggerService.createComponent(component, module); } } catch (Exception e) { throw new RuntimeException(e); } } } }