2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.intellij
.util
.indexing
;
19 import com
.intellij
.openapi
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.util
.io
.FileUtil
;
21 import com
.intellij
.util
.CommonProcessors
;
22 import com
.intellij
.util
.Processor
;
23 import com
.intellij
.util
.containers
.SLRUCache
;
24 import com
.intellij
.util
.io
.DataExternalizer
;
25 import com
.intellij
.util
.io
.DataInputOutputUtil
;
26 import com
.intellij
.util
.io
.KeyDescriptor
;
27 import com
.intellij
.util
.io
.PersistentHashMap
;
28 import gnu
.trove
.TIntHashSet
;
29 import org
.jetbrains
.annotations
.NotNull
;
32 import java
.util
.ArrayList
;
33 import java
.util
.Collection
;
34 import java
.util
.Iterator
;
35 import java
.util
.List
;
36 import java
.util
.concurrent
.locks
.Lock
;
37 import java
.util
.concurrent
.locks
.ReentrantLock
;
40 * @author Eugene Zhuravlev
43 public final class MapIndexStorage
<Key
, Value
> implements IndexStorage
<Key
, Value
>{
44 private static final Logger LOG
= Logger
.getInstance("#com.intellij.util.indexing.MapIndexStorage");
45 private PersistentHashMap
<Key
, ValueContainer
<Value
>> myMap
;
46 private SLRUCache
<Key
, ChangeTrackingValueContainer
<Value
>> myCache
;
47 private final File myStorageFile
;
48 private final KeyDescriptor
<Key
> myKeyDescriptor
;
49 private final ValueContainerExternalizer
<Value
> myValueContainerExternalizer
;
50 private final int myCacheSize
;
52 private final Lock l
= new ReentrantLock();
54 public MapIndexStorage(File storageFile
, final KeyDescriptor
<Key
> keyDescriptor
, final DataExternalizer
<Value
> valueExternalizer
,
55 final int cacheSize
) throws IOException
{
57 myStorageFile
= storageFile
;
58 myKeyDescriptor
= keyDescriptor
;
59 myValueContainerExternalizer
= new ValueContainerExternalizer
<Value
>(valueExternalizer
);
60 myCacheSize
= cacheSize
;
64 private void initMapAndCache() throws IOException
{
65 final PersistentHashMap
<Key
, ValueContainer
<Value
>> map
=
66 new PersistentHashMap
<Key
, ValueContainer
<Value
>>(myStorageFile
, myKeyDescriptor
, myValueContainerExternalizer
);
67 myCache
= new SLRUCache
<Key
, ChangeTrackingValueContainer
<Value
>>(myCacheSize
, (int)(Math
.ceil(myCacheSize
* 0.25)) /* 25% from the main cache size*/) {
69 public ChangeTrackingValueContainer
<Value
> createValue(final Key key
) {
70 return new ChangeTrackingValueContainer
<Value
>(new ChangeTrackingValueContainer
.Initializer
<Value
>() {
71 public Object
getLock() {
75 public ValueContainer
<Value
> compute() {
76 ValueContainer
<Value
> value
= null;
80 value
= new ValueContainerImpl
<Value
>();
83 catch (IOException e
) {
84 throw new RuntimeException(e
);
91 protected void onDropFromCache(final Key key
, final ChangeTrackingValueContainer
<Value
> valueContainer
) {
92 if (!valueContainer
.isDirty()) {
96 if (!valueContainer
.needsCompacting()) {
97 final ByteArrayOutputStream bytes
= new ByteArrayOutputStream();
98 //noinspection IOResourceOpenedButNotSafelyClosed
99 final DataOutputStream _out
= new DataOutputStream(bytes
);
100 final TIntHashSet set
= valueContainer
.getInvalidated();
101 if (set
.size() > 0) {
102 for (int inputId
: set
.toArray()) {
103 myValueContainerExternalizer
.saveInvalidateCommand(_out
, inputId
);
106 final ValueContainer
<Value
> toRemove
= valueContainer
.getRemovedDelta();
107 if (toRemove
.size() > 0) {
108 myValueContainerExternalizer
.saveAsRemoved(_out
, toRemove
);
111 final ValueContainer
<Value
> toAppend
= valueContainer
.getAddedDelta();
112 if (toAppend
.size() > 0) {
113 myValueContainerExternalizer
.save(_out
, toAppend
);
116 map
.appendData(key
, new PersistentHashMap
.ValueDataAppender() {
117 public void append(final DataOutput out
) throws IOException
{
118 final byte[] barr
= bytes
.toByteArray();
124 // rewrite the value container for defragmentation
125 map
.put(key
, valueContainer
);
128 catch (IOException e
) {
129 throw new RuntimeException(e
);
137 public void flush() {
140 if (!myMap
.isClosed()) {
150 public void close() throws StorageException
{
155 catch (IOException e
) {
156 throw new StorageException(e
);
158 catch (RuntimeException e
) {
159 final Throwable cause
= e
.getCause();
160 if (cause
instanceof IOException
) {
161 throw new StorageException(cause
);
163 if (cause
instanceof StorageException
) {
164 throw (StorageException
)cause
;
170 public void clear() throws StorageException
{
174 catch (IOException e
) {
178 FileUtil
.delete(myStorageFile
);
181 catch (IOException e
) {
182 throw new StorageException(e
);
184 catch (RuntimeException e
) {
185 final Throwable cause
= e
.getCause();
186 if (cause
instanceof IOException
) {
187 throw new StorageException(cause
);
189 if (cause
instanceof StorageException
) {
190 throw (StorageException
)cause
;
196 public boolean processKeys(final Processor
<Key
> processor
) throws StorageException
{
199 myCache
.clear(); // this will ensure that all new keys are made into the map
200 return myMap
.processKeys(processor
);
202 catch (IOException e
) {
203 throw new StorageException(e
);
205 catch (RuntimeException e
) {
206 final Throwable cause
= e
.getCause();
207 if (cause
instanceof IOException
) {
208 throw new StorageException(cause
);
210 if (cause
instanceof StorageException
) {
211 throw (StorageException
)cause
;
220 public Collection
<Key
> getKeys() throws StorageException
{
221 List
<Key
> keys
= new ArrayList
<Key
>();
222 processKeys(new CommonProcessors
.CollectProcessor
<Key
>(keys
));
227 public ChangeTrackingValueContainer
<Value
> read(final Key key
) throws StorageException
{
230 return myCache
.get(key
);
232 catch (RuntimeException e
) {
233 final Throwable cause
= e
.getCause();
234 if (cause
instanceof IOException
) {
235 throw new StorageException(cause
);
237 if (cause
instanceof StorageException
) {
238 throw (StorageException
)cause
;
247 public void addValue(final Key key
, final int inputId
, final Value value
) throws StorageException
{
248 read(key
).addValue(inputId
, value
);
251 public void removeValue(final Key key
, final int inputId
, final Value value
) throws StorageException
{
252 read(key
).removeValue(inputId
, value
);
255 public void removeAllValues(Key key
, int inputId
) throws StorageException
{
256 // important: assuming the key exists in the index
257 read(key
).removeAllValues(inputId
);
260 private static final class ValueContainerExternalizer
<T
> implements DataExternalizer
<ValueContainer
<T
>> {
261 private final DataExternalizer
<T
> myExternalizer
;
263 private ValueContainerExternalizer(DataExternalizer
<T
> externalizer
) {
264 myExternalizer
= externalizer
;
267 public void save(final DataOutput out
, final ValueContainer
<T
> container
) throws IOException
{
268 saveImpl(out
, container
, false);
271 public void saveAsRemoved(final DataOutput out
, final ValueContainer
<T
> container
) throws IOException
{
272 saveImpl(out
, container
, true);
275 public void saveInvalidateCommand(final DataOutput out
, int inputId
) throws IOException
{
276 DataInputOutputUtil
.writeSINT(out
, -inputId
);
279 private void saveImpl(final DataOutput out
, final ValueContainer
<T
> container
, final boolean asRemovedData
) throws IOException
{
280 DataInputOutputUtil
.writeSINT(out
, container
.size());
281 for (final Iterator
<T
> valueIterator
= container
.getValueIterator(); valueIterator
.hasNext();) {
282 final T value
= valueIterator
.next();
283 myExternalizer
.save(out
, value
);
285 final ValueContainer
.IntIterator ids
= container
.getInputIdsIterator(value
);
287 DataInputOutputUtil
.writeSINT(out
, ids
.size());
288 while (ids
.hasNext()) {
289 final int id
= ids
.next();
290 DataInputOutputUtil
.writeSINT(out
, asRemovedData ?
-id
: id
);
294 DataInputOutputUtil
.writeSINT(out
, 0);
299 public ValueContainerImpl
<T
> read(final DataInput in
) throws IOException
{
300 DataInputStream stream
= (DataInputStream
)in
;
301 final ValueContainerImpl
<T
> valueContainer
= new ValueContainerImpl
<T
>();
303 while (stream
.available() > 0) {
304 final int valueCount
= DataInputOutputUtil
.readSINT(in
);
305 if (valueCount
< 0) {
306 valueContainer
.removeAllValues(-valueCount
);
307 valueContainer
.setNeedsCompacting(true);
310 for (int valueIdx
= 0; valueIdx
< valueCount
; valueIdx
++) {
311 final T value
= myExternalizer
.read(in
);
312 final int idCount
= DataInputOutputUtil
.readSINT(in
);
313 for (int i
= 0; i
< idCount
; i
++) {
314 final int id
= DataInputOutputUtil
.readSINT(in
);
316 valueContainer
.removeValue(-id
, value
);
317 valueContainer
.setNeedsCompacting(true);
320 valueContainer
.addValue(id
, value
);
326 return valueContainer
;