All Downloads are FREE. Search and download functionalities are using the official Maven repository.

roboguice.RoboGuice Maven / Gradle / Ivy

There is a newer version: 4.0.0
Show newest version
package roboguice;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;

import roboguice.config.DefaultRoboModule;
import roboguice.config.RoboGuiceHierarchyTraversalFilter;
import roboguice.event.EventManager;
import roboguice.inject.ContextScope;
import roboguice.inject.ContextScopedRoboInjector;
import roboguice.inject.ResourceListener;
import roboguice.inject.RoboInjector;
import roboguice.inject.ViewListener;
import roboguice.util.Strings;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.google.inject.HierarchyTraversalFilter;
import com.google.inject.HierarchyTraversalFilterFactory;
import com.google.inject.Module;
import com.google.inject.internal.util.Stopwatch;
import com.google.inject.util.Modules;

import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;

/**
 *
 * Manages injectors for RoboGuice applications.
 *
 * There are two types of injectors:
 *
 * 1. The base application injector, which is not typically used directly by the user.
 * 2. The ContextScopedInjector, which is obtained by calling {@link #getInjector(android.content.Context)}, and knows about
 *    your current context, whether it's an activity, service, or something else.
 * 
 * BUG hashmap should also key off of stage and modules list
 * TODO add documentation about annotation processing system
 */
public final class RoboGuice {
    @edu.umd.cs.findbugs.annotations.SuppressWarnings("MS_SHOULD_BE_FINAL")
    @SuppressWarnings({"checkstyle:visibilitymodifier","checkstyle:staticvariablename"})
    public static Stage DEFAULT_STAGE = Stage.PRODUCTION;

    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MS_SHOULD_BE_FINAL")
    protected static WeakHashMap injectors = new WeakHashMap();
    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MS_SHOULD_BE_FINAL")
    protected static WeakHashMap resourceListeners = new WeakHashMap();
    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MS_SHOULD_BE_FINAL")
    protected static WeakHashMap viewListeners = new WeakHashMap();


    /** Enables or disables using annotation databases to optimize roboguice. Used for testing. Enabled by default.*/
    private static boolean useAnnotationDatabases = true;

    //used for testing
    static {
        String useAnnotationsEnvVar = System.getenv("roboguice.useAnnotationDatabases");
        if( useAnnotationsEnvVar != null ) {
            useAnnotationDatabases = Boolean.parseBoolean(useAnnotationsEnvVar);
        }
    }

    private RoboGuice() {
    }

    /**
     * Return the cached Injector instance for this application, or create a new one if necessary.
     */
    public static Injector getOrCreateBaseApplicationInjector(Application application) {
        Injector rtrn = injectors.get(application);
        if( rtrn!=null )
            return rtrn;

        synchronized (RoboGuice.class) {
            rtrn = injectors.get(application);
            if( rtrn!=null )
                return rtrn;

            return getOrCreateBaseApplicationInjector(application, DEFAULT_STAGE);
        }
    }

    /**
     * Return the cached Injector instance for this application, or create a new one if necessary.
     * If specifying your own modules, you must include a DefaultRoboModule for most things to work properly.
     * Do something like the following:
     *
     * RoboGuice.setApplicationInjector( app, RoboGuice.DEFAULT_STAGE, Modules.override(RoboGuice.newDefaultRoboModule(app)).with(new MyModule() );
     *
     * @see com.google.inject.util.Modules#override(com.google.inject.Module...)
     * @see roboguice.RoboGuice#createBaseApplicationInjector(android.app.Application, com.google.inject.Stage, String[], com.google.inject.Module...)
     * @see roboguice.RoboGuice#newDefaultRoboModule(android.app.Application)
     * @see roboguice.RoboGuice#DEFAULT_STAGE
     *
     * If using this method with test cases, be sure to call {@link roboguice.RoboGuice.Util#reset()} in your test teardown methods
     * to avoid polluting our other tests with your custom injector.  Don't do this in your real application though.
     * One of RoboGuice's entry points.
     */
    public static Injector getOrCreateBaseApplicationInjector(final Application application, Stage stage, Module... modules ) {
        final Stopwatch stopwatch = new Stopwatch();
        synchronized (RoboGuice.class) {
            initializeAnnotationDatabaseFinderAndHierarchyTraversalFilterFactory(application);
            return createGuiceInjector(application, stage, stopwatch, modules);
        }
    }

    /**
     * Shortcut to obtain an injector during tests. It will load all modules declared in manifest, add a {@code DefaultRoboModule},
     * and override all bindings defined in test modules. We use default stage by default.
     *
     * RoboGuice.overrideApplicationInjector( app, new TestModule() );
     *
     * @see com.google.inject.util.Modules#override(com.google.inject.Module...)
     * @see roboguice.RoboGuice#getOrCreateBaseApplicationInjector(android.app.Application, com.google.inject.Stage, com.google.inject.Module...)
     * @see roboguice.RoboGuice#newDefaultRoboModule(android.app.Application)
     * @see roboguice.RoboGuice#DEFAULT_STAGE
     *
     * If using this method with test cases, be sure to call {@link roboguice.RoboGuice.Util#reset()} in your test teardown methods
     * to avoid polluting our other tests with your custom injector.  Don't do this in your real application though.
     */
    public static Injector overrideApplicationInjector(final Application application,  Module... overrideModules) {
        synchronized (RoboGuice.class) {
            final List baseModules = extractModulesFromManifest(application);
            final Injector rtrn = Guice.createInjector(DEFAULT_STAGE, Modules.override(baseModules).with(overrideModules));
            injectors.put(application,rtrn);
            return rtrn;
        }
    }

    /**
     * Return the cached Injector instance for this application, or create a new one if necessary.
     * One of RoboGuice's entry points.
     */
    public static Injector getOrCreateBaseApplicationInjector(Application application, Stage stage) {
        final Stopwatch stopwatch = new Stopwatch();

        synchronized (RoboGuice.class) {
            initializeAnnotationDatabaseFinderAndHierarchyTraversalFilterFactory(application);
            final List modules = extractModulesFromManifest(application);
            return createGuiceInjector(application, stage, stopwatch, modules.toArray(new Module[modules.size()]));
        }
    }

    private static List extractModulesFromManifest(Application application) {
        try {
            final ArrayList modules = new ArrayList();

            final ApplicationInfo ai = application.getPackageManager().getApplicationInfo(application.getPackageName(), PackageManager.GET_META_DATA);
            final Bundle bundle = ai.metaData;
            final String roboguiceModules = bundle!=null ? bundle.getString("roboguice.modules") : null;
            final DefaultRoboModule defaultRoboModule = newDefaultRoboModule(application);
            final String[] moduleNames = roboguiceModules!=null ? roboguiceModules.split("[\\s,]") : new String[]{};

            modules.add(defaultRoboModule);

            for (String name : moduleNames) {
                if( Strings.notEmpty(name)) {
                    final Class clazz = Class.forName(name).asSubclass(Module.class);
                    try {
                        modules.add(clazz.getDeclaredConstructor(Application.class).newInstance(application));
                    } catch( NoSuchMethodException ignored ) {
                        modules.add( clazz.newInstance() );
                    }
                }
            }
            return modules;
        } catch (Exception e) {
            throw new RuntimeException("Unable to instantiate your Module.  Check your roboguice.modules metadata in your AndroidManifest.xml",e);
        }  
    }

    private static Injector createGuiceInjector(final Application application, Stage stage, final Stopwatch stopwatch, Module... modules) {
        try {
            synchronized (RoboGuice.class) {
                final Injector rtrn = Guice.createInjector(stage, modules);
                injectors.put(application,rtrn);
                return rtrn;
            }
        } finally {
            stopwatch.resetAndLog("BaseApplicationInjector creation");
        }
    }

    public static RoboInjector getInjector(Context context) {
        final Application application = (Application)context.getApplicationContext();
        return new ContextScopedRoboInjector(context, getOrCreateBaseApplicationInjector(application));
    }

    /**
     * A shortcut for RoboGuice.getInjector(context).injectMembers(o);
     */
    public static  T injectMembers( Context context, T t ) {
        getInjector(context).injectMembers(t);
        return t;
    }

    public static DefaultRoboModule newDefaultRoboModule(final Application application) {
        return new DefaultRoboModule(application, new ContextScope(application), getViewListener(application), getResourceListener(application));
    }

    public static void setUseAnnotationDatabases(boolean useAnnotationDatabases) {
        RoboGuice.useAnnotationDatabases = useAnnotationDatabases;
    }

    @SuppressWarnings("ConstantConditions")
    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NP_LOAD_OF_KNOWN_NULL_VALUE", justification="Double check lock")
    protected static ResourceListener getResourceListener( Application application ) {
        ResourceListener resourceListener = resourceListeners.get(application);
        if( resourceListener==null ) {
            synchronized (RoboGuice.class) {
                if( resourceListener==null ) {
                    resourceListener = new ResourceListener(application);
                    resourceListeners.put(application,resourceListener);
                }
            }
        }
        return resourceListener;
    }

    @SuppressWarnings("ConstantConditions")
    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NP_LOAD_OF_KNOWN_NULL_VALUE", justification="Double check lock")
    protected static ViewListener getViewListener( final Application application ) {
        ViewListener viewListener = viewListeners.get(application);
        if( viewListener==null ) {
            synchronized (RoboGuice.class) {
                if( viewListener==null ) {
                    viewListener = new ViewListener();
                    viewListeners.put(application,viewListener);
                }
            }
        }
        return viewListener;
    }

    public static void destroyInjector(Context context) {
        final RoboInjector injector = getInjector(context);
        injector.getInstance(EventManager.class).destroy();
        //noinspection SuspiciousMethodCalls
        injectors.remove(context); // it's okay, Context is an Application
    }

    private static void initializeAnnotationDatabaseFinderAndHierarchyTraversalFilterFactory(Application application) {
        if( useAnnotationDatabases ) {
            Log.d(RoboGuice.class.getName(), "Using annotation database(s).");
            long start = SystemClock.currentThreadTimeMillis();

            try {
                Set packageNameList = new HashSet();

                try {
                    ApplicationInfo ai = application.getPackageManager().getApplicationInfo(application.getPackageName(), PackageManager.GET_META_DATA);
                    final Bundle bundle = ai.metaData;
                    final String roboguicePackages = bundle!=null ? bundle.getString("roboguice.annotations.packages") : null;
                    if( roboguicePackages != null ) {
                        for( String packageName : roboguicePackages.split("[\\s,]") ) {
                            packageNameList.add(packageName);
                        }
                    }
                } catch (NameNotFoundException e) {
                    //if no packages are found in manifest, just log
                    Log.d(RoboGuice.class.getName(), "Failed to read manifest properly.");
                    e.printStackTrace();
                }

                if( packageNameList.isEmpty() ) {
                    //add default package if none is specified
                    packageNameList.add("");
                }
                packageNameList.add("roboguice");
                Log.d(RoboGuice.class.getName(), "Using annotation database(s) : " + packageNameList.toString());



                final String[] packageNames = new String[packageNameList.size()];
                packageNameList.toArray(packageNames);

                Guice.setAnnotationDatabasePackageNames(packageNames);
                long end = SystemClock.currentThreadTimeMillis();
                Log.d(RoboGuice.class.getName(), "Time spent loading annotation databases : " + (end-start));
            } catch( Exception ex ) {
                throw new IllegalStateException("Unable to use annotation database(s)", ex);
            }
        } else {
            Log.d(RoboGuice.class.getName(), "Using full reflection. Try using RoboGuice annotation processor for better performance.");
            Guice.setHierarchyTraversalFilterFactory(new HierarchyTraversalFilterFactory() {
                @Override
                public HierarchyTraversalFilter createHierarchyTraversalFilter() {
                    return new RoboGuiceHierarchyTraversalFilter();
                }
            });
        }
    }

    public static final class Util {
        private Util() {}

        /**
         * This method is provided to reset RoboGuice in testcases.
         * It should not be called in a real application.
         */
        public static void reset() {
            injectors.clear();
            resourceListeners.clear();
            viewListeners.clear();
            //clear annotation database finder
            //restore hierarchy filter
            Guice.setAnnotationDatabasePackageNames(null);
            Guice.setHierarchyTraversalFilterFactory(new HierarchyTraversalFilterFactory());
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy