PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/platform/util/src/com/intellij/util/indexing/impl/ValueContainerImpl.java

http://github.com/JetBrains/intellij-community
Java | 580 lines | 466 code | 83 blank | 31 comment | 113 complexity | 8265f81f5910c7f227a5b48c908fc500 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  1. /*
  2. * Copyright 2000-2016 JetBrains s.r.o.
  3. *
  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
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  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.
  15. */
  16. package com.intellij.util.indexing.impl;
  17. import com.intellij.openapi.diagnostic.Logger;
  18. import com.intellij.util.SmartList;
  19. import com.intellij.util.indexing.ValueContainer;
  20. import com.intellij.util.indexing.containers.ChangeBufferingList;
  21. import com.intellij.util.indexing.containers.IntIdsIterator;
  22. import com.intellij.util.io.DataExternalizer;
  23. import com.intellij.util.io.DataInputOutputUtil;
  24. import gnu.trove.THashMap;
  25. import org.jetbrains.annotations.ApiStatus;
  26. import org.jetbrains.annotations.NotNull;
  27. import org.jetbrains.annotations.Nullable;
  28. import java.io.DataInputStream;
  29. import java.io.DataOutput;
  30. import java.io.IOException;
  31. import java.util.Iterator;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.NoSuchElementException;
  35. /**
  36. * @author Eugene Zhuravlev
  37. */
  38. @ApiStatus.Internal
  39. public class ValueContainerImpl<Value> extends UpdatableValueContainer<Value> implements Cloneable{
  40. private static final Logger LOG = Logger.getInstance(ValueContainerImpl.class);
  41. private static final Object myNullValue = new Object();
  42. // there is no volatile as we modify under write lock and read under read lock
  43. // Most often (80%) we store 0 or one mapping, then we store them in two fields: myInputIdMapping, myInputIdMappingValue
  44. // when there are several value mapped, myInputIdMapping is ValueToInputMap<Value, Data> (it's actually just THashMap), myInputIdMappingValue = null
  45. private Object myInputIdMapping;
  46. private Object myInputIdMappingValue;
  47. @Override
  48. public void addValue(int inputId, Value value) {
  49. final Object fileSetObject = getFileSetObject(value);
  50. if (fileSetObject == null) {
  51. attachFileSetForNewValue(value, inputId);
  52. }
  53. else if (fileSetObject instanceof Integer) {
  54. int existingValue = ((Integer)fileSetObject).intValue();
  55. if (existingValue != inputId) {
  56. ChangeBufferingList list = new ChangeBufferingList();
  57. list.add(existingValue);
  58. list.add(inputId);
  59. resetFileSetForValue(value, list);
  60. }
  61. }
  62. else {
  63. ((ChangeBufferingList)fileSetObject).add(inputId);
  64. }
  65. }
  66. @Nullable
  67. private ValueToInputMap<Value> asMapping() {
  68. //noinspection unchecked
  69. return myInputIdMapping instanceof ValueToInputMap ? (ValueToInputMap<Value>)myInputIdMapping : null;
  70. }
  71. private Value asValue() {
  72. //noinspection unchecked
  73. return (Value)myInputIdMapping;
  74. }
  75. private Value nullValue() {
  76. //noinspection unchecked
  77. return (Value)myNullValue;
  78. }
  79. private void resetFileSetForValue(Value value, @NotNull Object fileSet) {
  80. if (value == null) value = nullValue();
  81. Map<Value, Object> map = asMapping();
  82. if (map == null) {
  83. myInputIdMappingValue = fileSet;
  84. }
  85. else {
  86. map.put(value, fileSet);
  87. }
  88. }
  89. @Override
  90. public int size() {
  91. return myInputIdMapping != null ? myInputIdMapping instanceof ValueToInputMap ? ((ValueToInputMap<?>)myInputIdMapping).size(): 1 : 0;
  92. }
  93. @Override
  94. public void removeAssociatedValue(int inputId) {
  95. if (myInputIdMapping == null) return;
  96. List<Object> fileSetObjects = null;
  97. List<Value> valueObjects = null;
  98. for (final InvertedIndexValueIterator<Value> valueIterator = getValueIterator(); valueIterator.hasNext();) {
  99. final Value value = valueIterator.next();
  100. if (valueIterator.getValueAssociationPredicate().contains(inputId)) {
  101. if (fileSetObjects == null) {
  102. fileSetObjects = new SmartList<>();
  103. valueObjects = new SmartList<>();
  104. }
  105. else if (IndexDebugAssertions.DEBUG) {
  106. LOG.error("Expected only one value per-inputId for " + IndexDebugAssertions.DEBUG_INDEX_ID.get(), String.valueOf(fileSetObjects.get(0)), String.valueOf(value));
  107. }
  108. fileSetObjects.add(valueIterator.getFileSetObject());
  109. valueObjects.add(value);
  110. }
  111. }
  112. if (fileSetObjects != null) {
  113. for (int i = 0, len = valueObjects.size(); i < len; ++i) {
  114. removeValue(inputId, fileSetObjects.get(i), valueObjects.get(i));
  115. }
  116. }
  117. }
  118. void removeValue(int inputId, Value value) {
  119. removeValue(inputId, getFileSetObject(value), value);
  120. }
  121. private void removeValue(int inputId, Object fileSet, Value value) {
  122. if (fileSet == null) {
  123. return;
  124. }
  125. if (fileSet instanceof ChangeBufferingList) {
  126. final ChangeBufferingList changesList = (ChangeBufferingList)fileSet;
  127. changesList.remove(inputId);
  128. if (!changesList.isEmpty()) return;
  129. }
  130. else if (fileSet instanceof Integer) {
  131. if (((Integer)fileSet).intValue() != inputId) {
  132. return;
  133. }
  134. }
  135. Map<Value, Object> mapping = asMapping();
  136. if (mapping == null) {
  137. myInputIdMapping = null;
  138. myInputIdMappingValue = null;
  139. }
  140. else {
  141. mapping.remove(value);
  142. if (mapping.size() == 1) {
  143. Value mappingValue = mapping.keySet().iterator().next();
  144. myInputIdMapping = mappingValue;
  145. Object inputIdMappingValue = mapping.get(mappingValue);
  146. // prevent NPEs on file set due to Value class being mutable or having inconsistent equals wrt disk persistence
  147. // (instance that is serialized and new instance created with deserialization from the same bytes are expected to be equal)
  148. myInputIdMappingValue = inputIdMappingValue != null ? inputIdMappingValue : new Integer(0);
  149. }
  150. }
  151. }
  152. @NotNull
  153. @Override
  154. public InvertedIndexValueIterator<Value> getValueIterator() {
  155. if (myInputIdMapping == null) {
  156. //noinspection unchecked
  157. return (InvertedIndexValueIterator<Value>)EmptyValueIterator.INSTANCE;
  158. }
  159. Map<Value, Object> mapping = asMapping();
  160. if (mapping == null) {
  161. return new InvertedIndexValueIterator<Value>() {
  162. private Value value = asValue();
  163. @NotNull
  164. @Override
  165. public IntIterator getInputIdsIterator() {
  166. return getIntIteratorOutOfFileSetObject(getFileSetObject());
  167. }
  168. @NotNull
  169. @Override
  170. public IntPredicate getValueAssociationPredicate() {
  171. return getPredicateOutOfFileSetObject(getFileSetObject());
  172. }
  173. @Override
  174. public Object getFileSetObject() {
  175. return myInputIdMappingValue;
  176. }
  177. @Override
  178. public boolean hasNext() {
  179. return value != null;
  180. }
  181. @Override
  182. public Value next() {
  183. Value next = value;
  184. if (next == myNullValue) next = null;
  185. value = null;
  186. return next;
  187. }
  188. @Override
  189. public void remove() {
  190. throw new UnsupportedOperationException();
  191. }
  192. };
  193. }
  194. else {
  195. return new InvertedIndexValueIterator<Value>() {
  196. private Value current;
  197. private Object currentValue;
  198. private final Iterator<Map.Entry<Value, Object>> iterator = mapping.entrySet().iterator();
  199. @Override
  200. public boolean hasNext() {
  201. return iterator.hasNext();
  202. }
  203. @Override
  204. public Value next() {
  205. Map.Entry<Value, Object> entry = iterator.next();
  206. current = entry.getKey();
  207. Value next = current;
  208. currentValue = entry.getValue();
  209. if (next == myNullValue) next = null;
  210. return next;
  211. }
  212. @Override
  213. public void remove() {
  214. throw new UnsupportedOperationException();
  215. }
  216. @NotNull
  217. @Override
  218. public IntIterator getInputIdsIterator() {
  219. return getIntIteratorOutOfFileSetObject(getFileSetObject());
  220. }
  221. @NotNull
  222. @Override
  223. public IntPredicate getValueAssociationPredicate() {
  224. return getPredicateOutOfFileSetObject(getFileSetObject());
  225. }
  226. @Override
  227. public Object getFileSetObject() {
  228. if (current == null) throw new IllegalStateException();
  229. return currentValue;
  230. }
  231. };
  232. }
  233. }
  234. private static class EmptyValueIterator<Value> implements InvertedIndexValueIterator<Value> {
  235. private static final EmptyValueIterator<Object> INSTANCE = new EmptyValueIterator<>();
  236. @NotNull
  237. @Override
  238. public ValueContainer.IntIterator getInputIdsIterator() {
  239. throw new IllegalStateException();
  240. }
  241. @NotNull
  242. @Override
  243. public IntPredicate getValueAssociationPredicate() {
  244. throw new IllegalStateException();
  245. }
  246. @Override
  247. public Object getFileSetObject() {
  248. throw new IllegalStateException();
  249. }
  250. @Override
  251. public boolean hasNext() {
  252. return false;
  253. }
  254. @Override
  255. public Value next() {
  256. throw new NoSuchElementException();
  257. }
  258. @Override
  259. public void remove() {
  260. throw new IllegalStateException();
  261. }
  262. }
  263. @NotNull
  264. private static IntPredicate getPredicateOutOfFileSetObject(@Nullable Object input) {
  265. if (input == null) return EMPTY_PREDICATE;
  266. if (input instanceof Integer) {
  267. final int singleId = (Integer)input;
  268. return id -> id == singleId;
  269. }
  270. return ((ChangeBufferingList)input).intPredicate();
  271. }
  272. @NotNull
  273. private static
  274. ValueContainer.IntIterator getIntIteratorOutOfFileSetObject(@Nullable Object input) {
  275. if (input == null) return EMPTY_ITERATOR;
  276. if (input instanceof Integer) {
  277. return new SingleValueIterator(((Integer)input).intValue());
  278. }
  279. return ((ChangeBufferingList)input).intIterator();
  280. }
  281. private Object getFileSetObject(Value value) {
  282. if (myInputIdMapping == null) return null;
  283. value = value != null ? value : nullValue();
  284. if (myInputIdMapping == value || // myNullValue is Object
  285. myInputIdMapping.equals(value)) {
  286. return myInputIdMappingValue;
  287. }
  288. Map<Value, Object> mapping = asMapping();
  289. return mapping == null ? null : mapping.get(value);
  290. }
  291. @Override
  292. public ValueContainerImpl<Value> clone() {
  293. try {
  294. //noinspection unchecked
  295. ValueContainerImpl<Value> clone = (ValueContainerImpl<Value>)super.clone();
  296. ValueToInputMap<Value> mapping = asMapping();
  297. if (mapping != null) {
  298. final ValueToInputMap<Value> cloned = mapping.clone();
  299. cloned.forEachEntry((key, val) -> {
  300. if (val instanceof ChangeBufferingList) {
  301. cloned.put(key, ((ChangeBufferingList)val).clone());
  302. }
  303. return true;
  304. });
  305. clone.myInputIdMapping = cloned;
  306. }
  307. else if (myInputIdMappingValue instanceof ChangeBufferingList) {
  308. clone.myInputIdMappingValue = ((ChangeBufferingList)myInputIdMappingValue).clone();
  309. }
  310. return clone;
  311. }
  312. catch (CloneNotSupportedException e) {
  313. throw new RuntimeException(e);
  314. }
  315. }
  316. private static final ValueContainer.IntIterator EMPTY_ITERATOR = new IntIdsIterator() {
  317. @Override
  318. public boolean hasNext() {
  319. return false;
  320. }
  321. @Override
  322. public int next() {
  323. return 0;
  324. }
  325. @Override
  326. public int size() {
  327. return 0;
  328. }
  329. @Override
  330. public boolean hasAscendingOrder() {
  331. return true;
  332. }
  333. @Override
  334. public IntIdsIterator createCopyInInitialState() {
  335. return this;
  336. }
  337. };
  338. @Nullable
  339. private ChangeBufferingList ensureFileSetCapacityForValue(Value value, int count) {
  340. if (count <= 1) return null;
  341. Object fileSetObject = getFileSetObject(value);
  342. if (fileSetObject != null) {
  343. if (fileSetObject instanceof Integer) {
  344. ChangeBufferingList list = new ChangeBufferingList(count + 1);
  345. list.add(((Integer)fileSetObject).intValue());
  346. resetFileSetForValue(value, list);
  347. return list;
  348. }
  349. if (fileSetObject instanceof ChangeBufferingList) {
  350. ChangeBufferingList list = (ChangeBufferingList)fileSetObject;
  351. list.ensureCapacity(count);
  352. return list;
  353. }
  354. return null;
  355. }
  356. final ChangeBufferingList fileSet = new ChangeBufferingList(count);
  357. attachFileSetForNewValue(value, fileSet);
  358. return fileSet;
  359. }
  360. private void attachFileSetForNewValue(Value value, Object fileSet) {
  361. value = value != null ? value : nullValue();
  362. if (myInputIdMapping != null) {
  363. Map<Value, Object> mapping = asMapping();
  364. if (mapping == null) {
  365. Value oldMapping = asValue();
  366. myInputIdMapping = mapping = new ValueToInputMap<>(2);
  367. mapping.put(oldMapping, myInputIdMappingValue);
  368. myInputIdMappingValue = null;
  369. }
  370. mapping.put(value, fileSet);
  371. }
  372. else {
  373. myInputIdMapping = value;
  374. myInputIdMappingValue = fileSet;
  375. }
  376. }
  377. @Override
  378. public void saveTo(DataOutput out, DataExternalizer<? super Value> externalizer) throws IOException {
  379. DataInputOutputUtil.writeINT(out, size());
  380. for (final InvertedIndexValueIterator<Value> valueIterator = getValueIterator(); valueIterator.hasNext();) {
  381. final Value value = valueIterator.next();
  382. externalizer.save(out, value);
  383. Object fileSetObject = valueIterator.getFileSetObject();
  384. if (fileSetObject instanceof Integer) {
  385. DataInputOutputUtil.writeINT(out, (Integer)fileSetObject); // most common 90% case during index building
  386. } else {
  387. // serialize positive file ids with delta encoding
  388. ChangeBufferingList originalInput = (ChangeBufferingList)fileSetObject;
  389. IntIdsIterator intIterator = originalInput.sortedIntIterator();
  390. if (IndexDebugAssertions.DEBUG) IndexDebugAssertions.assertTrue(intIterator.hasAscendingOrder());
  391. if (intIterator.size() == 1) {
  392. DataInputOutputUtil.writeINT(out, intIterator.next());
  393. } else {
  394. DataInputOutputUtil.writeINT(out, -intIterator.size());
  395. int prev = 0;
  396. while (intIterator.hasNext()) {
  397. int fileId = intIterator.next();
  398. DataInputOutputUtil.writeINT(out, fileId - prev);
  399. prev = fileId;
  400. }
  401. }
  402. }
  403. }
  404. }
  405. static final int NUMBER_OF_VALUES_THRESHOLD = 20;
  406. public void readFrom(@NotNull DataInputStream stream,
  407. @NotNull DataExternalizer<? extends Value> externalizer,
  408. @NotNull ValueContainerInputRemapping remapping) throws IOException {
  409. FileId2ValueMapping<Value> mapping = null;
  410. while (stream.available() > 0) {
  411. final int valueCount = DataInputOutputUtil.readINT(stream);
  412. if (valueCount < 0) {
  413. // ChangeTrackingValueContainer marked inputId as invalidated, see ChangeTrackingValueContainer.saveTo
  414. final int[] inputIds = remapping.remap(-valueCount);
  415. if (mapping == null && size() > NUMBER_OF_VALUES_THRESHOLD) { // avoid O(NumberOfValues)
  416. mapping = new FileId2ValueMapping<>(this);
  417. }
  418. boolean doCompact = false;
  419. for (int inputId : inputIds) {
  420. if(mapping != null) {
  421. if (mapping.removeFileId(inputId)) doCompact = true;
  422. } else {
  423. removeAssociatedValue(inputId);
  424. doCompact = true;
  425. }
  426. }
  427. if (doCompact) setNeedsCompacting(true);
  428. }
  429. else {
  430. for (int valueIdx = 0; valueIdx < valueCount; valueIdx++) {
  431. final Value value = externalizer.read(stream);
  432. int idCountOrSingleValue = DataInputOutputUtil.readINT(stream);
  433. if (idCountOrSingleValue > 0) {
  434. int[] inputIds = remapping.remap(idCountOrSingleValue);
  435. for (int inputId : inputIds) {
  436. addValue(inputId, value);
  437. if (mapping != null) mapping.associateFileIdToValue(inputId, value);
  438. }
  439. } else {
  440. idCountOrSingleValue = -idCountOrSingleValue;
  441. ChangeBufferingList changeBufferingList = ensureFileSetCapacityForValue(value, idCountOrSingleValue);
  442. int prev = 0;
  443. for (int i = 0; i < idCountOrSingleValue; i++) {
  444. final int id = DataInputOutputUtil.readINT(stream);
  445. int[] inputIds = remapping.remap(prev + id);
  446. for (int inputId : inputIds) {
  447. if (changeBufferingList != null) changeBufferingList.add(inputId);
  448. else addValue(inputId, value);
  449. if (mapping != null) mapping.associateFileIdToValue(inputId, value);
  450. }
  451. prev += id;
  452. }
  453. }
  454. }
  455. }
  456. }
  457. }
  458. private static class SingleValueIterator implements IntIdsIterator {
  459. private final int myValue;
  460. private boolean myValueRead;
  461. private SingleValueIterator(int value) {
  462. myValue = value;
  463. }
  464. @Override
  465. public boolean hasNext() {
  466. return !myValueRead;
  467. }
  468. @Override
  469. public int next() {
  470. myValueRead = true;
  471. return myValue;
  472. }
  473. @Override
  474. public int size() {
  475. return 1;
  476. }
  477. @Override
  478. public boolean hasAscendingOrder() {
  479. return true;
  480. }
  481. @Override
  482. public IntIdsIterator createCopyInInitialState() {
  483. return new SingleValueIterator(myValue);
  484. }
  485. }
  486. private static final IntPredicate EMPTY_PREDICATE = __ -> false;
  487. // a class to distinguish a difference between user-value with THashMap type and internal value container
  488. private static class ValueToInputMap<Value> extends THashMap<Value, Object> {
  489. ValueToInputMap(int size) {
  490. super(size);
  491. }
  492. @Override
  493. public ValueToInputMap<Value> clone() {
  494. return (ValueToInputMap<Value>)super.clone();
  495. }
  496. }
  497. }