Report abuse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package net.peierls.util;

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.ProvisionException;
import com.google.inject.spi.DefaultBindingScopingVisitor;

/**
 * A special form of singleton scope that eagerly starts building its
 * singletons concurrently in background using a thread pool at injector
 * creation.
 */
public class ConcurrentSingletonScope implements Scope {

    /**
     * Install this scope given a Binder. Call only once per injector.
     */
    public static void install(Binder binder) {
        Scope scope = new ConcurrentSingletonScope();
        binder.bindScope(ConcurrentSingleton.class, scope);
        binder.requestInjection(scope);
    }


    // Scope method

    public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {

        // Memoize call to unscoped.get() under key.

        return new Provider<T>() {
            public T get() {
                ConcurrentMap<Key<T>, Future<T>> futures = futures();
                Future<T> f = futures.get(key);
                if (f == null) {
                    FutureTask<T> creator = new FutureTask<T>(new Callable<T>() {
                        public T call() {
                            return unscoped.get();
                        }
                    });
                    f = futures.putIfAbsent(key, creator);
                    if (f == null) {
                        f = creator;
                        creator.run();
                    }
                }
                try {
                    return f.get();
                } catch (ExecutionException e) {
                    throw (RuntimeException) e.getCause();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new ProvisionException("interrupted during provision");
                }
            }
        };
    }

    //
    // Implementation
    //

    private ConcurrentSingletonScope() {
        // Prevent instantiation by anyone else.
    }

    @Inject private void initializeBindingsInThisScope(Injector injector) {
        final Scope thisScope = this;
        final ExecutorService pool = Executors.newCachedThreadPool();
        for (final Binding<?> binding : injector.getBindings().values()) {
            binding.acceptScopingVisitor(new DefaultBindingScopingVisitor<Void>() {
                @Override public Void visitScope(Scope scope) {
                    if (scope == thisScope) {
                        pool.execute(new Runnable() {
                            public void run() {
                                binding.getProvider().get();
                            }
                        });
                    }
                    return null;
                }
            });
        }
        pool.shutdown();
    }

    @SuppressWarnings("unchecked")
    <T> ConcurrentMap<Key<T>, Future<T>> futures() {
        return (ConcurrentMap<Key<T>, Future<T>>) futures;
    }

    private final ConcurrentMap futures = new ConcurrentHashMap();
}