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.intellij.openapi.util.Computable;
020    import com.intellij.util.Consumer;
021    import com.intellij.util.Function;
022    import com.intellij.util.containers.ConcurrentWeakValueHashMap;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.diagnostics.Diagnostic;
026    import org.jetbrains.jet.lang.resolve.BindingContext;
027    import org.jetbrains.jet.lang.resolve.BindingTrace;
028    import org.jetbrains.jet.util.slicedmap.ReadOnlySlice;
029    import org.jetbrains.jet.util.slicedmap.WritableSlice;
030    import org.jetbrains.jet.utils.Nulls;
031    
032    import java.util.Collection;
033    import java.util.concurrent.ConcurrentHashMap;
034    import java.util.concurrent.ConcurrentMap;
035    
036    public class LockBasedStorageManager implements StorageManager {
037    
038        private final Object lock = new Object() {
039            @Override
040            public String toString() {
041                return "LockBasedStorageManager centralized lock";
042            }
043        };
044    
045        @NotNull
046        @Override
047        public <K, V> MemoizedFunctionToNotNull<K, V> createMemoizedFunction(
048                @NotNull Function<K, V> compute, @NotNull ReferenceKind valuesReferenceKind
049        ) {
050            ConcurrentMap<K, Object> map = createConcurrentMap(valuesReferenceKind);
051            return new MapBasedMemoizedFunctionToNotNull<K, V>(lock, map, compute);
052        }
053    
054        @NotNull
055        @Override
056        public <K, V> MemoizedFunctionToNullable<K, V> createMemoizedFunctionWithNullableValues(
057                @NotNull Function<K, V> compute, @NotNull ReferenceKind valuesReferenceKind
058        ) {
059            ConcurrentMap<K, Object> map = createConcurrentMap(valuesReferenceKind);
060            return new MapBasedMemoizedFunction<K, V>(lock, map, compute);
061        }
062    
063        private static <K, V> ConcurrentMap<K, V> createConcurrentMap(ReferenceKind referenceKind) {
064            return (referenceKind == ReferenceKind.WEAK) ? new ConcurrentWeakValueHashMap<K, V>() : new ConcurrentHashMap<K, V>();
065        }
066    
067        @NotNull
068        @Override
069        public <T> NotNullLazyValue<T> createLazyValue(@NotNull Computable<T> computable) {
070            return new LockBasedNotNullLazyValue<T>(lock, computable);
071        }
072    
073        @NotNull
074        @Override
075        public <T> NotNullLazyValue<T> createLazyValueWithPostCompute(@NotNull Computable<T> computable, @NotNull final Consumer<T> postCompute) {
076            return new LockBasedNotNullLazyValue<T>(lock, computable) {
077                @Override
078                protected void postCompute(@NotNull T value) {
079                    postCompute.consume(value);
080                }
081            };
082        }
083    
084        @NotNull
085        @Override
086        public <T> NullableLazyValue<T> createNullableLazyValue(@NotNull Computable<T> computable) {
087            return new LockBasedLazyValue<T>(lock, computable);
088        }
089    
090        @NotNull
091        @Override
092        public <T> NullableLazyValue<T> createNullableLazyValueWithPostCompute(
093                @NotNull Computable<T> computable, @NotNull final Consumer<T> postCompute
094        ) {
095            return new LockBasedLazyValue<T>(lock, computable) {
096                @Override
097                protected void postCompute(@Nullable T value) {
098                    postCompute.consume(value);
099                }
100            };
101        }
102    
103        @NotNull
104        @Override
105        public BindingTrace createSafeTrace(@NotNull BindingTrace originalTrace) {
106            // It seems safe to have a separate lock for traces:
107            // no other locks will be acquired inside the trace operations
108            return new LockProtectedTrace(lock, originalTrace);
109        }
110    
111        private static class LockBasedLazyValue<T> implements NullableLazyValue<T> {
112            private final Object lock;
113            private final Computable<T> computable;
114    
115            @Nullable
116            private volatile Object value = null;
117    
118            public LockBasedLazyValue(@NotNull Object lock, @NotNull Computable<T> computable) {
119                this.lock = lock;
120                this.computable = computable;
121            }
122    
123            @Override
124            public T compute() {
125                Object _value = value;
126                if (_value != null) return Nulls.unescape(_value);
127    
128                synchronized (lock) {
129                    _value = value;
130                    if (_value != null) return Nulls.unescape(_value);
131    
132                    T typedValue = computable.compute();
133                    value = Nulls.escape(typedValue);
134    
135                    postCompute(typedValue);
136    
137                    return typedValue;
138                }
139            }
140    
141            protected void postCompute(T value) {
142                // Doing something in post-compute helps prevent infinite recursion
143            }
144        }
145    
146        private static class LockBasedNotNullLazyValue<T> extends LockBasedLazyValue<T> implements NotNullLazyValue<T> {
147    
148            public LockBasedNotNullLazyValue(@NotNull Object lock, @NotNull Computable<T> computable) {
149                super(lock, computable);
150            }
151    
152            @Override
153            @NotNull
154            public T compute() {
155                T result = super.compute();
156                assert result != null : "compute() returned null";
157                return result;
158            }
159        }
160    
161        private static class MapBasedMemoizedFunction<K, V> implements MemoizedFunctionToNullable<K, V> {
162            private final Object lock;
163            private final ConcurrentMap<K, Object> cache;
164            private final Function<K, V> compute;
165    
166            public MapBasedMemoizedFunction(@NotNull Object lock, @NotNull ConcurrentMap<K, Object> map, @NotNull Function<K, V> compute) {
167                this.lock = lock;
168                this.cache = map;
169                this.compute = compute;
170            }
171    
172            @Override
173            @Nullable
174            public V fun(@NotNull K input) {
175                Object value = cache.get(input);
176                if (value != null) return Nulls.unescape(value);
177    
178                synchronized (lock) {
179                    value = cache.get(input);
180                    if (value != null) return Nulls.unescape(value);
181    
182                    V typedValue = compute.fun(input);
183    
184                    Object oldValue = cache.put(input, Nulls.escape(typedValue));
185                    assert oldValue == null : "Race condition detected";
186    
187                    return typedValue;
188                }
189            }
190        }
191    
192        private static class MapBasedMemoizedFunctionToNotNull<K, V> extends MapBasedMemoizedFunction<K, V> implements MemoizedFunctionToNotNull<K, V> {
193    
194            public MapBasedMemoizedFunctionToNotNull(
195                    @NotNull Object lock,
196                    @NotNull ConcurrentMap<K, Object> map,
197                    @NotNull Function<K, V> compute
198            ) {
199                super(lock, map, compute);
200            }
201    
202            @NotNull
203            @Override
204            public V fun(@NotNull K input) {
205                V result = super.fun(input);
206                assert result != null : "compute() returned null";
207                return result;
208            }
209        }
210    
211        private static class LockProtectedTrace implements BindingTrace {
212            private final Object lock;
213            private final BindingTrace trace;
214    
215            public LockProtectedTrace(@NotNull Object lock, @NotNull BindingTrace trace) {
216                this.lock = lock;
217                this.trace = trace;
218            }
219    
220            @Override
221            public BindingContext getBindingContext() {
222                synchronized (lock) {
223                    return trace.getBindingContext();
224                }
225            }
226    
227            @Override
228            public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
229                synchronized (lock) {
230                    trace.record(slice, key, value);
231                }
232            }
233    
234            @Override
235            public <K> void record(WritableSlice<K, Boolean> slice, K key) {
236                synchronized (lock) {
237                    trace.record(slice, key);
238                }
239            }
240    
241            @Override
242            @Nullable
243            public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
244                synchronized (lock) {
245                    return trace.get(slice, key);
246                }
247            }
248    
249            @Override
250            @NotNull
251            public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
252                synchronized (lock) {
253                    return trace.getKeys(slice);
254                }
255            }
256    
257            @Override
258            public void report(@NotNull Diagnostic diagnostic) {
259                synchronized (lock) {
260                    trace.report(diagnostic);
261                }
262            }
263        }
264    
265    }