001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.lang.resolve.lazy.storage;
018    
019    import com.google.common.collect.ImmutableMap;
020    import com.intellij.openapi.util.Computable;
021    import com.intellij.util.Consumer;
022    import com.intellij.util.Function;
023    import com.intellij.util.containers.ConcurrentWeakValueHashMap;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.annotations.TestOnly;
027    import org.jetbrains.jet.lang.diagnostics.Diagnostic;
028    import org.jetbrains.jet.lang.resolve.BindingContext;
029    import org.jetbrains.jet.lang.resolve.BindingTrace;
030    import org.jetbrains.jet.util.slicedmap.ReadOnlySlice;
031    import org.jetbrains.jet.util.slicedmap.WritableSlice;
032    import org.jetbrains.jet.utils.ExceptionUtils;
033    import org.jetbrains.jet.utils.WrappedValues;
034    
035    import java.util.Collection;
036    import java.util.concurrent.ConcurrentHashMap;
037    import java.util.concurrent.ConcurrentMap;
038    
039    public class LockBasedStorageManager implements StorageManager {
040    
041        private final Object lock = new Object() {
042            @Override
043            public String toString() {
044                return "LockBasedStorageManager centralized lock";
045            }
046        };
047    
048        @NotNull
049        @Override
050        public <K, V> MemoizedFunctionToNotNull<K, V> createMemoizedFunction(
051                @NotNull Function<K, V> compute, @NotNull ReferenceKind valuesReferenceKind
052        ) {
053            ConcurrentMap<K, Object> map = createConcurrentMap(valuesReferenceKind);
054            return new MapBasedMemoizedFunctionToNotNull<K, V>(lock, map, compute);
055        }
056    
057        @NotNull
058        @Override
059        public <K, V> MemoizedFunctionToNullable<K, V> createMemoizedFunctionWithNullableValues(
060                @NotNull Function<K, V> compute, @NotNull ReferenceKind valuesReferenceKind
061        ) {
062            ConcurrentMap<K, Object> map = createConcurrentMap(valuesReferenceKind);
063            return new MapBasedMemoizedFunction<K, V>(lock, map, compute);
064        }
065    
066        private static <K, V> ConcurrentMap<K, V> createConcurrentMap(ReferenceKind referenceKind) {
067            return (referenceKind == ReferenceKind.WEAK) ? new ConcurrentWeakValueHashMap<K, V>() : new ConcurrentHashMap<K, V>();
068        }
069    
070        @NotNull
071        @Override
072        public <T> NotNullLazyValue<T> createLazyValue(@NotNull Computable<T> computable) {
073            return new LockBasedNotNullLazyValue<T>(lock, computable);
074        }
075    
076        @NotNull
077        @Override
078        public <T> NotNullLazyValue<T> createLazyValueWithPostCompute(@NotNull Computable<T> computable, @NotNull final Consumer<T> postCompute) {
079            return new LockBasedNotNullLazyValue<T>(lock, computable) {
080                @Override
081                protected void postCompute(@NotNull T value) {
082                    postCompute.consume(value);
083                }
084            };
085        }
086    
087        @NotNull
088        @Override
089        public <T> NullableLazyValue<T> createNullableLazyValue(@NotNull Computable<T> computable) {
090            return new LockBasedLazyValue<T>(lock, computable);
091        }
092    
093        @NotNull
094        @Override
095        public <T> NullableLazyValue<T> createNullableLazyValueWithPostCompute(
096                @NotNull Computable<T> computable, @NotNull final Consumer<T> postCompute
097        ) {
098            return new LockBasedLazyValue<T>(lock, computable) {
099                @Override
100                protected void postCompute(@Nullable T value) {
101                    postCompute.consume(value);
102                }
103            };
104        }
105    
106        @NotNull
107        @Override
108        public BindingTrace createSafeTrace(@NotNull BindingTrace originalTrace) {
109            // It seems safe to have a separate lock for traces:
110            // no other locks will be acquired inside the trace operations
111            return new LockProtectedTrace(lock, originalTrace);
112        }
113    
114        @Override
115        public <T> T compute(@NotNull Computable<T> computable) {
116            synchronized (lock) {
117                return computable.compute();
118            }
119        }
120    
121        private static class LockBasedLazyValue<T> implements NullableLazyValue<T> {
122            private final Object lock;
123            private final Computable<T> computable;
124    
125            @Nullable
126            private volatile Object value = null;
127    
128            public LockBasedLazyValue(@NotNull Object lock, @NotNull Computable<T> computable) {
129                this.lock = lock;
130                this.computable = computable;
131            }
132    
133            @Override
134            public T compute() {
135                Object _value = value;
136                if (_value != null) return WrappedValues.unescapeExceptionOrNull(_value);
137    
138                synchronized (lock) {
139                    _value = value;
140                    if (_value != null) return WrappedValues.unescapeExceptionOrNull(_value);
141    
142                    try {
143                        T typedValue = computable.compute();
144                        value = WrappedValues.escapeNull(typedValue);
145                        postCompute(typedValue);
146                        return typedValue;
147                    }
148                    catch (Throwable throwable) {
149                        value = WrappedValues.escapeThrowable(throwable);
150                        throw ExceptionUtils.rethrow(throwable);
151                    }
152                }
153            }
154    
155            protected void postCompute(T value) {
156                // Doing something in post-compute helps prevent infinite recursion
157            }
158        }
159    
160        private static class LockBasedNotNullLazyValue<T> extends LockBasedLazyValue<T> implements NotNullLazyValue<T> {
161    
162            public LockBasedNotNullLazyValue(@NotNull Object lock, @NotNull Computable<T> computable) {
163                super(lock, computable);
164            }
165    
166            @Override
167            @NotNull
168            public T compute() {
169                T result = super.compute();
170                assert result != null : "compute() returned null";
171                return result;
172            }
173        }
174    
175        private static class MapBasedMemoizedFunction<K, V> implements MemoizedFunctionToNullable<K, V> {
176            private final Object lock;
177            private final ConcurrentMap<K, Object> cache;
178            private final Function<K, V> compute;
179    
180            public MapBasedMemoizedFunction(@NotNull Object lock, @NotNull ConcurrentMap<K, Object> map, @NotNull Function<K, V> compute) {
181                this.lock = lock;
182                this.cache = map;
183                this.compute = compute;
184            }
185    
186            @Override
187            @Nullable
188            public V fun(@NotNull K input) {
189                Object value = cache.get(input);
190                if (value != null) return WrappedValues.unescapeExceptionOrNull(value);
191    
192                synchronized (lock) {
193                    value = cache.get(input);
194                    if (value != null) return WrappedValues.unescapeExceptionOrNull(value);
195    
196                    try {
197                        V typedValue = compute.fun(input);
198                        Object oldValue = cache.put(input, WrappedValues.escapeNull(typedValue));
199                        assert oldValue == null : "Race condition detected";
200    
201                        return typedValue;
202                    }
203                    catch (Throwable throwable) {
204                        Object oldValue = cache.put(input, WrappedValues.escapeThrowable(throwable));
205                        assert oldValue == null : "Race condition detected";
206    
207                        throw ExceptionUtils.rethrow(throwable);
208                    }
209                }
210            }
211        }
212    
213        private static class MapBasedMemoizedFunctionToNotNull<K, V> extends MapBasedMemoizedFunction<K, V> implements MemoizedFunctionToNotNull<K, V> {
214    
215            public MapBasedMemoizedFunctionToNotNull(
216                    @NotNull Object lock,
217                    @NotNull ConcurrentMap<K, Object> map,
218                    @NotNull Function<K, V> compute
219            ) {
220                super(lock, map, compute);
221            }
222    
223            @NotNull
224            @Override
225            public V fun(@NotNull K input) {
226                V result = super.fun(input);
227                assert result != null : "compute() returned null";
228                return result;
229            }
230        }
231    
232        private static class LockProtectedContext implements BindingContext {
233            private final Object lock;
234            private final BindingContext context;
235    
236            private LockProtectedContext(Object lock, BindingContext context) {
237                this.lock = lock;
238                this.context = context;
239            }
240    
241            @Override
242            public Collection<Diagnostic> getDiagnostics() {
243                synchronized (lock) {
244                    return context.getDiagnostics();
245                }
246            }
247    
248            @Nullable
249            @Override
250            public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
251                synchronized (lock) {
252                    return context.get(slice, key);
253                }
254            }
255    
256            @NotNull
257            @Override
258            public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
259                synchronized (lock) {
260                    return context.getKeys(slice);
261                }
262            }
263    
264            @NotNull
265            @Override
266            @TestOnly
267            public <K, V> ImmutableMap<K, V> getSliceContents(@NotNull ReadOnlySlice<K, V> slice) {
268                synchronized (lock) {
269                    return context.getSliceContents(slice);
270                }
271            }
272        }
273    
274        private static class LockProtectedTrace implements BindingTrace {
275            private final Object lock;
276            private final BindingTrace trace;
277            private final BindingContext context;
278    
279            public LockProtectedTrace(@NotNull Object lock, @NotNull BindingTrace trace) {
280                this.lock = lock;
281                this.trace = trace;
282                this.context = new LockProtectedContext(lock, trace.getBindingContext());
283            }
284    
285            @Override
286            public BindingContext getBindingContext() {
287                return context;
288            }
289    
290            @Override
291            public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
292                synchronized (lock) {
293                    trace.record(slice, key, value);
294                }
295            }
296    
297            @Override
298            public <K> void record(WritableSlice<K, Boolean> slice, K key) {
299                synchronized (lock) {
300                    trace.record(slice, key);
301                }
302            }
303    
304            @Override
305            @Nullable
306            public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
307                synchronized (lock) {
308                    return trace.get(slice, key);
309                }
310            }
311    
312            @Override
313            @NotNull
314            public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
315                synchronized (lock) {
316                    return trace.getKeys(slice);
317                }
318            }
319    
320            @Override
321            public void report(@NotNull Diagnostic diagnostic) {
322                synchronized (lock) {
323                    trace.report(diagnostic);
324                }
325            }
326        }
327    
328    }