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
017package org.jetbrains.jet.lang.resolve.lazy.storage;
018
019import com.intellij.openapi.util.Computable;
020import com.intellij.util.Consumer;
021import com.intellij.util.Function;
022import com.intellij.util.containers.ConcurrentWeakValueHashMap;
023import org.jetbrains.annotations.NotNull;
024import org.jetbrains.annotations.Nullable;
025import org.jetbrains.jet.lang.diagnostics.Diagnostic;
026import org.jetbrains.jet.lang.resolve.BindingContext;
027import org.jetbrains.jet.lang.resolve.BindingTrace;
028import org.jetbrains.jet.util.slicedmap.ReadOnlySlice;
029import org.jetbrains.jet.util.slicedmap.WritableSlice;
030import org.jetbrains.jet.utils.Nulls;
031
032import java.util.Collection;
033import java.util.concurrent.ConcurrentHashMap;
034import java.util.concurrent.ConcurrentMap;
035
036public 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}