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
;
38 * @author Eugene Zhuravlev
41 public final class MapIndexStorage
<Key
, Value
> implements IndexStorage
<Key
, Value
>{
42 private static final Logger LOG
= Logger
.getInstance("#com.intellij.util.indexing.MapIndexStorage");
43 private PersistentHashMap
<Key
, ValueContainer
<Value
>> myMap
;
44 private SLRUCache
<Key
, ChangeTrackingValueContainer
<Value
>> myCache
;
45 private final File myStorageFile
;
46 private final KeyDescriptor
<Key
> myKeyDescriptor
;
47 private final ValueContainerExternalizer
<Value
> myValueContainerExternalizer
;
48 private final int myCacheSize
;
50 public MapIndexStorage(File storageFile
, final KeyDescriptor
<Key
> keyDescriptor
, final DataExternalizer
<Value
> valueExternalizer
,
51 final int cacheSize
) throws IOException
{
53 myStorageFile
= storageFile
;
54 myKeyDescriptor
= keyDescriptor
;
55 myValueContainerExternalizer
= new ValueContainerExternalizer
<Value
>(valueExternalizer
);
56 myCacheSize
= cacheSize
;
60 private void initMapAndCache() throws IOException
{
61 final PersistentHashMap
<Key
, ValueContainer
<Value
>> map
=
62 new PersistentHashMap
<Key
, ValueContainer
<Value
>>(myStorageFile
, myKeyDescriptor
, myValueContainerExternalizer
);
63 myCache
= new SLRUCache
<Key
, ChangeTrackingValueContainer
<Value
>>(myCacheSize
, (int)(Math
.ceil(myCacheSize
* 0.25)) /* 25% from the main cache size*/) {
65 public ChangeTrackingValueContainer
<Value
> createValue(final Key key
) {
66 return new ChangeTrackingValueContainer
<Value
>(new ChangeTrackingValueContainer
.Initializer
<Value
>() {
67 public Object
getLock() {
71 public ValueContainer
<Value
> compute() {
72 ValueContainer
<Value
> value
= null;
76 value
= new ValueContainerImpl
<Value
>();
79 catch (IOException e
) {
80 throw new RuntimeException(e
);
87 protected void onDropFromCache(final Key key
, final ChangeTrackingValueContainer
<Value
> valueContainer
) {
88 if (!valueContainer
.isDirty()) {
92 if (!valueContainer
.needsCompacting()) {
93 final ByteArrayOutputStream bytes
= new ByteArrayOutputStream();
94 //noinspection IOResourceOpenedButNotSafelyClosed
95 final DataOutputStream _out
= new DataOutputStream(bytes
);
96 final TIntHashSet set
= valueContainer
.getInvalidated();
98 for (int inputId
: set
.toArray()) {
99 myValueContainerExternalizer
.saveInvalidateCommand(_out
, inputId
);
102 final ValueContainer
<Value
> toRemove
= valueContainer
.getRemovedDelta();
103 if (toRemove
.size() > 0) {
104 myValueContainerExternalizer
.saveAsRemoved(_out
, toRemove
);
107 final ValueContainer
<Value
> toAppend
= valueContainer
.getAddedDelta();
108 if (toAppend
.size() > 0) {
109 myValueContainerExternalizer
.save(_out
, toAppend
);
112 map
.appendData(key
, new PersistentHashMap
.ValueDataAppender() {
113 public void append(final DataOutput out
) throws IOException
{
114 final byte[] barr
= bytes
.toByteArray();
120 // rewrite the value container for defragmentation
121 map
.put(key
, valueContainer
);
124 catch (IOException e
) {
125 throw new RuntimeException(e
);
133 public synchronized void flush() {
134 if (!myMap
.isClosed()) {
140 public void close() throws StorageException
{
145 catch (IOException e
) {
146 throw new StorageException(e
);
148 catch (RuntimeException e
) {
149 final Throwable cause
= e
.getCause();
150 if (cause
instanceof IOException
) {
151 throw new StorageException(cause
);
153 if (cause
instanceof StorageException
) {
154 throw (StorageException
)cause
;
160 public void clear() throws StorageException
{
164 catch (IOException e
) {
168 FileUtil
.delete(myStorageFile
);
171 catch (IOException e
) {
172 throw new StorageException(e
);
174 catch (RuntimeException e
) {
175 final Throwable cause
= e
.getCause();
176 if (cause
instanceof IOException
) {
177 throw new StorageException(cause
);
179 if (cause
instanceof StorageException
) {
180 throw (StorageException
)cause
;
186 public synchronized boolean processKeys(final Processor
<Key
> processor
) throws StorageException
{
188 myCache
.clear(); // this will ensure that all new keys are made into the map
189 return myMap
.processKeys(processor
);
191 catch (IOException e
) {
192 throw new StorageException(e
);
194 catch (RuntimeException e
) {
195 final Throwable cause
= e
.getCause();
196 if (cause
instanceof IOException
) {
197 throw new StorageException(cause
);
199 if (cause
instanceof StorageException
) {
200 throw (StorageException
)cause
;
206 public Collection
<Key
> getKeys() throws StorageException
{
207 List
<Key
> keys
= new ArrayList
<Key
>();
208 processKeys(new CommonProcessors
.CollectProcessor
<Key
>(keys
));
213 public synchronized ChangeTrackingValueContainer
<Value
> read(final Key key
) throws StorageException
{
215 return myCache
.get(key
);
217 catch (RuntimeException e
) {
218 final Throwable cause
= e
.getCause();
219 if (cause
instanceof IOException
) {
220 throw new StorageException(cause
);
222 if (cause
instanceof StorageException
) {
223 throw (StorageException
)cause
;
229 public void addValue(final Key key
, final int inputId
, final Value value
) throws StorageException
{
230 read(key
).addValue(inputId
, value
);
233 public void removeValue(final Key key
, final int inputId
, final Value value
) throws StorageException
{
234 read(key
).removeValue(inputId
, value
);
237 public void removeAllValues(Key key
, int inputId
) throws StorageException
{
238 // important: assuming the key exists in the index
239 read(key
).removeAllValues(inputId
);
242 private static final class ValueContainerExternalizer
<T
> implements DataExternalizer
<ValueContainer
<T
>> {
243 private final DataExternalizer
<T
> myExternalizer
;
245 private ValueContainerExternalizer(DataExternalizer
<T
> externalizer
) {
246 myExternalizer
= externalizer
;
249 public void save(final DataOutput out
, final ValueContainer
<T
> container
) throws IOException
{
250 saveImpl(out
, container
, false);
253 public void saveAsRemoved(final DataOutput out
, final ValueContainer
<T
> container
) throws IOException
{
254 saveImpl(out
, container
, true);
257 public void saveInvalidateCommand(final DataOutput out
, int inputId
) throws IOException
{
258 DataInputOutputUtil
.writeSINT(out
, -inputId
);
261 private void saveImpl(final DataOutput out
, final ValueContainer
<T
> container
, final boolean asRemovedData
) throws IOException
{
262 DataInputOutputUtil
.writeSINT(out
, container
.size());
263 for (final Iterator
<T
> valueIterator
= container
.getValueIterator(); valueIterator
.hasNext();) {
264 final T value
= valueIterator
.next();
265 myExternalizer
.save(out
, value
);
267 final ValueContainer
.IntIterator ids
= container
.getInputIdsIterator(value
);
269 DataInputOutputUtil
.writeSINT(out
, ids
.size());
270 while (ids
.hasNext()) {
271 final int id
= ids
.next();
272 DataInputOutputUtil
.writeSINT(out
, asRemovedData ?
-id
: id
);
276 DataInputOutputUtil
.writeSINT(out
, 0);
281 public ValueContainerImpl
<T
> read(final DataInput in
) throws IOException
{
282 DataInputStream stream
= (DataInputStream
)in
;
283 final ValueContainerImpl
<T
> valueContainer
= new ValueContainerImpl
<T
>();
285 while (stream
.available() > 0) {
286 final int valueCount
= DataInputOutputUtil
.readSINT(in
);
287 if (valueCount
< 0) {
288 valueContainer
.removeAllValues(-valueCount
);
289 valueContainer
.setNeedsCompacting(true);
292 for (int valueIdx
= 0; valueIdx
< valueCount
; valueIdx
++) {
293 final T value
= myExternalizer
.read(in
);
294 final int idCount
= DataInputOutputUtil
.readSINT(in
);
295 for (int i
= 0; i
< idCount
; i
++) {
296 final int id
= DataInputOutputUtil
.readSINT(in
);
298 valueContainer
.removeValue(-id
, value
);
299 valueContainer
.setNeedsCompacting(true);
302 valueContainer
.addValue(id
, value
);
308 return valueContainer
;