Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jdt.core/search/org')
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/AbstractTypeFactory.java40
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/DatabaseRef.java72
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IDestructable.java40
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IIndexFileLocation.java32
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java26
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java35
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IReader.java16
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/ITypeFactory.java64
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexFileLocation.java59
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/LongArray.java61
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java602
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdLinkedList.java120
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java185
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java93
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdRawLinkedList.java271
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Package.java52
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Pointer.java62
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/PrimitiveTypes.java69
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java593
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/StreamHasher.java235
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/BTree.java773
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Chunk.java324
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ChunkCache.java139
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBProperties.java261
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBStatus.java29
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java959
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/EmptyString.java101
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeComparator.java20
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeVisitor.java36
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IString.java128
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java46
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LongString.java245
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/MemoryStats.java232
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/NdStringSet.java190
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Package.java48
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ShortString.java296
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java61
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldByte.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldChar.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldDouble.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldFloat.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldInt.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldLong.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java192
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java188
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java112
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldPointer.java40
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchIndex.java298
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchKey.java123
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldShort.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldString.java74
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IDestructableField.java17
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java16
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IRefCountedField.java25
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java398
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/BindingToIndexConverter.java123
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java930
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/GenericSignatures.java82
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/HierarchicalASTVisitor.java1139
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexTester.java454
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java1069
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexerEvent.java29
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Messages.java25
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Package.java48
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/messages.properties1
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/ClasspathResolver.java25
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/FileFingerprint.java250
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/IndexFilter.java43
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java300
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaNames.java222
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java54
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java37
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java39
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java39
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java39
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java39
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java70
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java122
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdComplexTypeSignature.java201
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstant.java99
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java56
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantArray.java48
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantBoolean.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantByte.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantChar.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantClass.java56
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantDouble.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantEnum.java68
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantFloat.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantInt.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantLong.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantShort.java57
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantString.java58
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java192
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java61
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodId.java107
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java105
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java260
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTreeNode.java99
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java266
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java122
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java39
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java39
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java39
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeArgument.java88
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java61
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeId.java184
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeInterface.java55
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java111
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeSignature.java157
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java141
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdWorkspaceLocation.java66
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/Package.java44
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TagTreeReader.java134
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TypeRef.java97
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeDescriptor.java51
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java228
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/ITypeAnnotationBuilder.java32
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryField.java80
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryMethod.java182
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryNestedType.java42
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryType.java672
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryTypeAnnotation.java90
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/TypeAnnotationBuilder.java150
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayMap.java312
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayUtils.java522
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/PathMap.java225
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/UnindexedSearchScope.java97
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java37
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java9
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java13
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/IndexBasedJavaSearchEnvironment.java333
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java24
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java20
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java6
-rw-r--r--org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java36
136 files changed, 20202 insertions, 53 deletions
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/AbstractTypeFactory.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/AbstractTypeFactory.java
new file mode 100644
index 000000000..957e7cf45
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/AbstractTypeFactory.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.field.StructDef.DeletionSemantics;
+
+public abstract class AbstractTypeFactory<T> implements ITypeFactory<T> {
+ @Override
+ public void destructFields(Nd dom, long address) {
+ // No nested fields by default
+ }
+
+ @Override
+ public void destruct(Nd dom, long address) {
+ // Nothing to destruct by default
+ }
+
+ @Override
+ public boolean hasDestructor() {
+ return false;
+ }
+
+ @Override
+ public boolean isReadyForDeletion(Nd dom, long address) {
+ return false;
+ }
+
+ @Override
+ public DeletionSemantics getDeletionSemantics() {
+ return DeletionSemantics.EXPLICIT;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/DatabaseRef.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/DatabaseRef.java
new file mode 100644
index 000000000..044fe241d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/DatabaseRef.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import java.util.function.Supplier;
+
+/**
+ * Holds a reference to a database entity that may be retained across read locks. In normal circumstances, it
+ * is unsafe to retain a database address after a read lock is released since the object pointed to at that
+ * address may have been deleted in the meantime. This class addresses this problem by remembering both the
+ * address itself and enough information to determine whether that address is invalid and search for an
+ * equivalent object if the original is lost.
+ */
+public class DatabaseRef<T extends NdNode> implements Supplier<T> {
+ private final Nd nd;
+ private T lastResult;
+ private long writeCounter;
+ private final Supplier<T> searchFunction;
+
+ /**
+ * Constructs a new {@link DatabaseRef} that will search for its target using the given search function.
+ */
+ public DatabaseRef(Nd nd, Supplier<T> searchFunction) {
+ this.nd = nd;
+ this.searchFunction = searchFunction;
+ this.writeCounter = -1;
+ }
+
+ /**
+ * Constructs a new {@link DatabaseRef} that will search for its target using the given search function.
+ */
+ public DatabaseRef(Nd nd, Supplier<T> searchFunction, T initialResult) {
+ this.nd = nd;
+ this.searchFunction = searchFunction;
+ this.lastResult = initialResult;
+ this.writeCounter = this.nd.getWriteNumber();
+ }
+
+ /**
+ * Returns the referenced object or null if the object is no longer present in the database.
+ */
+ public T get() {
+ long ndWriteNumber = this.nd.getWriteNumber();
+ if (this.writeCounter == ndWriteNumber) {
+ return this.lastResult;
+ }
+
+ T result = this.searchFunction.get();
+ this.writeCounter = ndWriteNumber;
+ this.lastResult = result;
+ return result;
+ }
+
+ public Nd getNd() {
+ return this.nd;
+ }
+
+ /**
+ * Acquires a read lock. Callers must invoke close() on the result when done.
+ */
+ public IReader lock() {
+ return this.nd.acquireReadLock();
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IDestructable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IDestructable.java
new file mode 100644
index 000000000..5bc78e047
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IDestructable.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+/**
+ * This mix-in interface is implemented by database objects that require a custom
+ * destruction step.
+ */
+public interface IDestructable {
+ /**
+ * Intended to be implemented by objects which require a custom destruction step.
+ * This should normally not be invoked by clients, since custom destruction is just
+ * one step in tearing down an object. The normal way to tear down an object is
+ * {@link NdNode#delete}
+ * <p>
+ * If you are writing code that must run as part of delete (or are implementing part
+ * of the destruct method on a custom ITypeFactory)the correct steps to destructing
+ * an object are:
+ * <ul>
+ * <li>Invoke this destruct method (which serves the same purpose as the user-implemented
+ * portion of a C++ destructor)</li>
+ * <li>Invoke ITypeFactory.destructFields to destruct its fields (which serves the same
+ * purpose as the compiler-implemented portion of a C++ destructor)</li>
+ * <li>Invoke Database.free on its address to free up memory allocated for the object
+ * itself. (Which serves the same purpose as the memory deallocation step in
+ * the C++ delete operator)</li>
+ * </ul>
+ * <p>
+ * Normally, first two steps are performed together as part of ITypeFactory.destruct
+ */
+ void destruct();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IIndexFileLocation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IIndexFileLocation.java
new file mode 100644
index 000000000..8a805d042
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IIndexFileLocation.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import java.net.URI;
+
+/**
+ * Files in the index are (conceptually) partitioned into workspace and non-workspace (external) files. Two index file
+ * locations are considered equal if their URIs are equal.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface IIndexFileLocation {
+ /**
+ * Returns the URI of the indexed file (non-{@code null}).
+ */
+ public URI getURI();
+
+ /**
+ * Returns the workspace relative path of the file in the index or {@code null} if the file is not in the workspace.
+ */
+ public String getFullPath();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java
new file mode 100644
index 000000000..ef24eb6fb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdNode.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * Interface for all nodes that can be visited by a {@link INdVisitor}.
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface INdNode {
+
+ /**
+ * Visits the children of this node.
+ */
+ public void accept(INdVisitor visitor) throws IndexException;
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java
new file mode 100644
index 000000000..034e23302
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/INdVisitor.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.core.runtime.CoreException;
+
+public interface INdVisitor {
+
+ /**
+ * Walk the nodes in a {@link Nd}. Return true to visit the children of
+ * this node, or false to skip to the next sibling of this node.
+ * Throw CoreException to stop the visit.
+ *
+ * @param node being visited
+ * @return whether to visit children
+ */
+ public boolean visit(INdNode node) throws CoreException;
+
+ /**
+ * All children have been visited, about to go back to the parent.
+ *
+ * @param node that has just completed visitation
+ * @throws CoreException
+ */
+ public void leave(INdNode node) throws CoreException;
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IReader.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IReader.java
new file mode 100644
index 000000000..3e1c3211b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IReader.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+public interface IReader extends AutoCloseable {
+ @Override
+ void close();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/ITypeFactory.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/ITypeFactory.java
new file mode 100644
index 000000000..e387a4059
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/ITypeFactory.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.field.StructDef.DeletionSemantics;
+
+// TODO(sxenos): rename this to something like "StructDescriptor" -- it's more than a factory and the word
+// type is overloaded in JDT.
+public interface ITypeFactory<T> {
+ /**
+ * Invokes the delete method on all the fields of the object, and calls deleteFields on the superclass' type (if
+ * any). Does not perform any higher-level cleanup operations. This is only intended to be called from the
+ * deleteFields methods of a subtype or the delete method of this class.
+ * <p>
+ * When destructing a type with a superclass, the correct destruction behavior is:
+ * <ul>
+ * <li>External code invokes the delete method on ITypeFactory
+ * <li>The ITypeFactory.delete method calls an instance method on the class (typically called T#delete()), which
+ * performs high-level deletion operations (if any).
+ * <li>T.delete also calls T.super.delete() (if any)
+ * <li>ITypeFactory.delete calls ITypeFactory.deleteFields, which performs low-level deletion operations on the
+ * fields, then calls ITypeFactory.deleteFields on the base type.
+ * </ul>
+ */
+ void destructFields(Nd dom, long address);
+
+ T create(Nd dom, long address);
+
+ /**
+ * Invokes any cleanup code for this object. In particular, it deallocates any memory allocated by the type's
+ * fields. Does not free the memory at address, though. This is used for both objects which were allocated their own
+ * memory block and objects which are embedded as fields within a larger object. If the object was given its own
+ * memory block, it is the caller's responsibility to invoke free after calling this method.
+ */
+ void destruct(Nd dom, long address);
+
+ /**
+ * If this returns false, the delete and deleteFields methods both always do nothing.
+ */
+ boolean hasDestructor();
+
+ int getRecordSize();
+
+ Class<?> getElementClass();
+
+ /**
+ * Returns true if this object is orphaned. If the object is refcounted, this means the refcount is 0. If
+ * the object is deleted via an owner pointer, this means the owner pointer is null.
+ */
+ boolean isReadyForDeletion(Nd dom, long address);
+
+ /**
+ * Returns the deletion semantics used for this object.
+ */
+ DeletionSemantics getDeletionSemantics();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexFileLocation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexFileLocation.java
new file mode 100644
index 000000000..e3112146b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/IndexFileLocation.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import java.net.URI;
+
+/**
+ * An implementation of IIndexFileLocation.
+ */
+public class IndexFileLocation implements IIndexFileLocation {
+ private final URI uri;
+ private final String fullPath;
+
+ public IndexFileLocation(URI uri, String fullPath) {
+ if (uri == null)
+ throw new IllegalArgumentException();
+ this.uri = uri;
+ this.fullPath = fullPath;
+ }
+
+ @Override
+ public String getFullPath() {
+ return this.fullPath;
+ }
+
+ @Override
+ public URI getURI() {
+ return this.uri;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof IIndexFileLocation) {
+ return this.uri.equals(((IIndexFileLocation) obj).getURI());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.uri.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ if (this.fullPath == null) {
+ return this.uri.toString();
+ }
+ return this.fullPath.toString() + " (" + this.uri.toString() + ')'; //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/LongArray.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/LongArray.java
new file mode 100644
index 000000000..11d28f8d3
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/LongArray.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+/**
+ * Represents an array of long.
+ */
+public class LongArray {
+ private static final int MIN_CAPACITY = 8;
+ private long[] contents;
+ private int size;
+
+ long get(int index) {
+ if (index >= this.size) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+
+ return this.contents[index];
+ }
+
+ long removeLast() {
+ return this.contents[--this.size];
+ }
+
+ void addLast(long toAdd) {
+ ensureCapacity(this.size + 1);
+ this.contents[this.size++] = toAdd;
+ }
+
+ private void ensureCapacity(int capacity) {
+ if (this.contents == null) {
+ this.contents = new long[Math.max(MIN_CAPACITY, capacity)];
+ }
+
+ if (this.contents.length >= capacity) {
+ return;
+ }
+
+ int newSize = capacity * 2;
+ long[] newContents = new long[newSize];
+
+ System.arraycopy(this.contents, 0, newContents, 0, this.contents.length);
+ this.contents = newContents;
+ }
+
+ int size() {
+ return this.size;
+ }
+
+ public boolean isEmpty() {
+ return this.size == 0;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
new file mode 100644
index 000000000..fb02f1319
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Nd.java
@@ -0,0 +1,602 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jdt.internal.core.nd.db.ChunkCache;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * Network Database for storing semantic information.
+ */
+public class Nd {
+ private static final int CANCELLATION_CHECK_INTERVAL = 500;
+ private static final int BLOCKED_WRITE_LOCK_OUTPUT_INTERVAL = 30000;
+ private static final int LONG_WRITE_LOCK_REPORT_THRESHOLD = 1000;
+ private static final int LONG_READ_LOCK_WAIT_REPORT_THRESHOLD = 1000;
+ public static boolean sDEBUG_LOCKS= false;
+ public static boolean DEBUG_DUPLICATE_DELETIONS = false;
+
+ private final int currentVersion;
+ private final int maxVersion;
+ private final int minVersion;
+
+ public static int version(int major, int minor) {
+ return (major << 16) + minor;
+ }
+
+ /**
+ * Returns the version that shall be used when creating new databases.
+ */
+ public int getDefaultVersion() {
+ return this.currentVersion;
+ }
+
+ public boolean isSupportedVersion(int vers) {
+ return vers >= this.minVersion && vers <= this.maxVersion;
+ }
+
+ public int getMinSupportedVersion() {
+ return this.minVersion;
+ }
+
+ public int getMaxSupportedVersion() {
+ return this.maxVersion;
+ }
+
+ public static String versionString(int version) {
+ final int major= version >> 16;
+ final int minor= version & 0xffff;
+ return "" + major + '.' + minor; //$NON-NLS-1$
+ }
+
+ // Local caches
+ protected Database db;
+ private File fPath;
+ private final HashMap<Object, Object> fResultCache = new HashMap<>();
+
+ private final NdNodeTypeRegistry<NdNode> fNodeTypeRegistry;
+ private HashMap<Long, Object> pendingDeletions = new HashMap<>();
+
+ private IReader fReader = new IReader() {
+ @Override
+ public void close() {
+ releaseReadLock();
+ }
+ };
+
+ /**
+ * This long is incremented every time a change is written to the database. Can be used to determine if the database
+ * has changed.
+ */
+ private long fWriteNumber;
+
+ public Nd(File dbPath, NdNodeTypeRegistry<NdNode> nodeTypes, int minVersion, int maxVersion,
+ int currentVersion) throws IndexException {
+ this(dbPath, ChunkCache.getSharedInstance(), nodeTypes, minVersion, maxVersion, currentVersion);
+ }
+
+ public Nd(File dbPath, ChunkCache chunkCache, NdNodeTypeRegistry<NdNode> nodeTypes, int minVersion,
+ int maxVersion, int currentVersion) throws IndexException {
+ this.currentVersion = currentVersion;
+ this.maxVersion = maxVersion;
+ this.minVersion = minVersion;
+ this.fNodeTypeRegistry = nodeTypes;
+ loadDatabase(dbPath, chunkCache);
+ if (sDEBUG_LOCKS) {
+ this.fLockDebugging = new HashMap<>();
+ System.out.println("Debugging database Locks"); //$NON-NLS-1$
+ }
+ }
+
+ public File getPath() {
+ return this.fPath;
+ }
+
+ public long getWriteNumber() {
+ return this.fWriteNumber;
+ }
+
+ public void scheduleDeletion(long addressOfNodeToDelete) {
+ if (this.pendingDeletions.containsKey(addressOfNodeToDelete)) {
+ logDoubleDeletion(addressOfNodeToDelete);
+ return;
+ }
+
+ Object data = Boolean.TRUE;
+ if (DEBUG_DUPLICATE_DELETIONS) {
+ data = new RuntimeException();
+ }
+ this.pendingDeletions.put(addressOfNodeToDelete, data);
+ }
+
+ protected void logDoubleDeletion(long addressOfNodeToDelete) {
+ // Sometimes an object can be scheduled for deletion twice, if it is created and then discarded shortly
+ // afterward during indexing. This may indicate an inefficiency in the indexer but is not necessarily
+ // a bug.
+ // If you're debugging issues related to duplicate deletions, set DEBUG_DUPLICATE_DELETIONS to true
+ Package.log("Database object queued for deletion twice", new RuntimeException()); //$NON-NLS-1$
+ Object earlierData = this.pendingDeletions.get(addressOfNodeToDelete);
+ if (earlierData instanceof RuntimeException) {
+ RuntimeException exception = (RuntimeException) earlierData;
+
+ Package.log("Data associated with earlier deletion stack was:", exception); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Synchronously processes all pending deletions
+ */
+ public void processDeletions() {
+ while (!this.pendingDeletions.isEmpty()) {
+ long next = this.pendingDeletions.keySet().iterator().next();
+
+ deleteIfUnreferenced(next);
+
+ this.pendingDeletions.remove(next);
+ }
+ }
+
+ /**
+ * Returns whether this {@link Nd} can never be written to. Writable subclasses should return false.
+ */
+ protected boolean isPermanentlyReadOnly() {
+ return false;
+ }
+
+ private void loadDatabase(File dbPath, ChunkCache cache) throws IndexException {
+ this.fPath= dbPath;
+ final boolean lockDB= this.db == null || this.lockCount != 0;
+
+ clearCaches();
+ this.db = new Database(this.fPath, cache, getDefaultVersion(), isPermanentlyReadOnly());
+
+ this.db.setLocked(lockDB);
+ if (!isSupportedVersion()) {
+ Package.log("Index database is uses an unsupported version " + this.db.getVersion() //$NON-NLS-1$
+ + " Deleting and recreating.", null); //$NON-NLS-1$
+ this.db.close();
+ this.fPath.delete();
+ this.db = new Database(this.fPath, cache, getDefaultVersion(), isPermanentlyReadOnly());
+ this.db.setLocked(lockDB);
+ }
+ this.fWriteNumber = this.db.getLong(Database.WRITE_NUMBER_OFFSET);
+ this.db.setLocked(this.lockCount != 0);
+ }
+
+ public Database getDB() {
+ return this.db;
+ }
+
+ // Read-write lock rules. Readers don't conflict with other readers,
+ // Writers conflict with readers, and everyone conflicts with writers.
+ private final Object mutex = new Object();
+ private int lockCount;
+ private int waitingReaders;
+ private long lastWriteAccess= 0;
+ //private long lastReadAccess= 0;
+ private long timeWriteLockAcquired;
+
+ public IReader acquireReadLock() {
+ try {
+ long t = sDEBUG_LOCKS ? System.nanoTime() : 0;
+ synchronized (this.mutex) {
+ ++this.waitingReaders;
+ try {
+ while (this.lockCount < 0)
+ this.mutex.wait();
+ } finally {
+ --this.waitingReaders;
+ }
+ ++this.lockCount;
+ this.db.setLocked(true);
+
+ if (sDEBUG_LOCKS) {
+ t = (System.nanoTime() - t) / 1000000;
+ if (t >= LONG_READ_LOCK_WAIT_REPORT_THRESHOLD) {
+ System.out.println("Acquired index read lock after " + t + " ms wait."); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ incReadLock(this.fLockDebugging);
+ }
+ return this.fReader;
+ }
+ } catch (InterruptedException e) {
+ throw new OperationCanceledException();
+ }
+ }
+
+ public void releaseReadLock() {
+ synchronized (this.mutex) {
+ assert this.lockCount > 0: "No lock to release"; //$NON-NLS-1$
+ if (sDEBUG_LOCKS) {
+ decReadLock(this.fLockDebugging);
+ }
+
+ //this.lastReadAccess= System.currentTimeMillis();
+ if (this.lockCount > 0)
+ --this.lockCount;
+ this.mutex.notifyAll();
+ this.db.setLocked(this.lockCount != 0);
+ }
+ // A lock release probably means that some AST is going away. The result cache has to be
+ // cleared since it may contain objects belonging to the AST that is going away. A failure
+ // to release an AST object would cause a memory leak since the whole AST would remain
+ // pinned to memory.
+ // TODO(sprigogin): It would be more efficient to replace the global result cache with
+ // separate caches for each AST.
+ //clearResultCache();
+ }
+
+ /**
+ * Acquire a write lock on this {@link Nd}. Blocks until any existing read/write locks are released.
+ * @throws OperationCanceledException
+ * @throws IllegalStateException if this {@link Nd} is not writable
+ */
+ public void acquireWriteLock(IProgressMonitor monitor) {
+ try {
+ acquireWriteLock(0, monitor);
+ } catch (InterruptedException e) {
+ throw new OperationCanceledException();
+ }
+ }
+
+ /**
+ * Acquire a write lock on this {@link Nd}, giving up the specified number of read locks first. Blocks
+ * until any existing read/write locks are released.
+ * @throws InterruptedException
+ * @throws IllegalStateException if this {@link Nd} is not writable
+ */
+ public void acquireWriteLock(int giveupReadLocks, IProgressMonitor monitor) throws InterruptedException {
+ assert !isPermanentlyReadOnly();
+ synchronized (this.mutex) {
+ if (sDEBUG_LOCKS) {
+ incWriteLock(giveupReadLocks);
+ }
+
+ if (giveupReadLocks > 0) {
+ // give up on read locks
+ assert this.lockCount >= giveupReadLocks: "Not enough locks to release"; //$NON-NLS-1$
+ if (this.lockCount < giveupReadLocks) {
+ giveupReadLocks= this.lockCount;
+ }
+ } else {
+ giveupReadLocks= 0;
+ }
+
+ // Let the readers go first
+ long start= sDEBUG_LOCKS ? System.currentTimeMillis() : 0;
+ while (this.lockCount > giveupReadLocks || this.waitingReaders > 0) {
+ this.mutex.wait(CANCELLATION_CHECK_INTERVAL);
+ if (monitor != null && monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ if (sDEBUG_LOCKS) {
+ start = reportBlockedWriteLock(start, giveupReadLocks);
+ }
+ }
+ this.lockCount= -1;
+ if (sDEBUG_LOCKS)
+ this.timeWriteLockAcquired = System.currentTimeMillis();
+ this.db.setExclusiveLock();
+ }
+ }
+
+ public final void releaseWriteLock() {
+ releaseWriteLock(0, true);
+ }
+
+ @SuppressWarnings("nls")
+ public void releaseWriteLock(int establishReadLocks, boolean flush) {
+ boolean wasInterrupted = false;
+ // When all locks are released we can clear the result cache.
+ if (establishReadLocks == 0) {
+ processDeletions();
+ this.db.putLong(Database.WRITE_NUMBER_OFFSET, ++this.fWriteNumber);
+ clearResultCache();
+ }
+ try {
+ wasInterrupted = this.db.giveUpExclusiveLock(flush) || wasInterrupted;
+ } catch (IndexException e) {
+ Package.log(e);
+ }
+ assert this.lockCount == -1;
+ this.lastWriteAccess= System.currentTimeMillis();
+ synchronized (this.mutex) {
+ if (sDEBUG_LOCKS) {
+ long timeHeld = this.lastWriteAccess - this.timeWriteLockAcquired;
+ if (timeHeld >= LONG_WRITE_LOCK_REPORT_THRESHOLD) {
+ System.out.println("Index write lock held for " + timeHeld + " ms");
+ }
+ decWriteLock(establishReadLocks);
+ }
+
+ if (this.lockCount < 0)
+ this.lockCount= establishReadLocks;
+ this.mutex.notifyAll();
+ this.db.setLocked(this.lockCount != 0);
+ }
+
+ if (wasInterrupted) {
+ throw new OperationCanceledException();
+ }
+ }
+
+ public boolean hasWaitingReaders() {
+ synchronized (this.mutex) {
+ return this.waitingReaders > 0;
+ }
+ }
+
+ public long getLastWriteAccess() {
+ return this.lastWriteAccess;
+ }
+
+ public boolean isSupportedVersion() throws IndexException {
+ final int version = this.db.getVersion();
+ return version >= this.minVersion && version <= this.maxVersion;
+ }
+
+ public void close() throws IndexException {
+ this.db.close();
+ clearCaches();
+ }
+
+ private void clearCaches() {
+// fileIndex= null;
+// tagIndex = null;
+// indexOfDefectiveFiles= null;
+// indexOfFiledWithUnresolvedIncludes= null;
+// fLinkageIDCache.clear();
+ clearResultCache();
+ }
+
+ public void clearResultCache() {
+ synchronized (this.fResultCache) {
+ this.fResultCache.clear();
+ }
+ }
+
+ public Object getCachedResult(Object key) {
+ synchronized (this.fResultCache) {
+ return this.fResultCache.get(key);
+ }
+ }
+
+ public void putCachedResult(Object key, Object result) {
+ putCachedResult(key, result, true);
+ }
+
+ public Object putCachedResult(Object key, Object result, boolean replace) {
+ synchronized (this.fResultCache) {
+ Object old= this.fResultCache.put(key, result);
+ if (old != null && !replace) {
+ this.fResultCache.put(key, old);
+ return old;
+ }
+ return result;
+ }
+ }
+
+ public void removeCachedResult(Object key) {
+ synchronized (this.fResultCache) {
+ this.fResultCache.remove(key);
+ }
+ }
+
+ // For debugging lock issues
+ static class DebugLockInfo {
+ int fReadLocks;
+ int fWriteLocks;
+ List<StackTraceElement[]> fTraces= new ArrayList<>();
+
+ public int addTrace() {
+ this.fTraces.add(Thread.currentThread().getStackTrace());
+ return this.fTraces.size();
+ }
+
+ @SuppressWarnings("nls")
+ public void write(String threadName) {
+ System.out.println("Thread: '" + threadName + "': " + this.fReadLocks + " readlocks, " + this.fWriteLocks + " writelocks");
+ for (StackTraceElement[] trace : this.fTraces) {
+ System.out.println(" Stacktrace:");
+ for (StackTraceElement ste : trace) {
+ System.out.println(" " + ste);
+ }
+ }
+ }
+
+ public void inc(DebugLockInfo val) {
+ this.fReadLocks+= val.fReadLocks;
+ this.fWriteLocks+= val.fWriteLocks;
+ this.fTraces.addAll(val.fTraces);
+ }
+ }
+
+ // For debugging lock issues
+ private Map<Thread, DebugLockInfo> fLockDebugging;
+
+ // For debugging lock issues
+ private static DebugLockInfo getLockInfo(Map<Thread, DebugLockInfo> lockDebugging) {
+ assert sDEBUG_LOCKS;
+
+ Thread key = Thread.currentThread();
+ DebugLockInfo result= lockDebugging.get(key);
+ if (result == null) {
+ result= new DebugLockInfo();
+ lockDebugging.put(key, result);
+ }
+ return result;
+ }
+
+ // For debugging lock issues
+ static void incReadLock(Map<Thread, DebugLockInfo> lockDebugging) {
+ DebugLockInfo info = getLockInfo(lockDebugging);
+ info.fReadLocks++;
+ if (info.addTrace() > 10) {
+ outputReadLocks(lockDebugging);
+ }
+ }
+
+ // For debugging lock issues
+ @SuppressWarnings("nls")
+ static void decReadLock(Map<Thread, DebugLockInfo> lockDebugging) throws AssertionError {
+ DebugLockInfo info = getLockInfo(lockDebugging);
+ if (info.fReadLocks <= 0) {
+ outputReadLocks(lockDebugging);
+ throw new AssertionError("Superfluous releaseReadLock");
+ }
+ if (info.fWriteLocks != 0) {
+ outputReadLocks(lockDebugging);
+ throw new AssertionError("Releasing readlock while holding write lock");
+ }
+ if (--info.fReadLocks == 0) {
+ lockDebugging.remove(Thread.currentThread());
+ } else {
+ info.addTrace();
+ }
+ }
+
+ // For debugging lock issues
+ @SuppressWarnings("nls")
+ private void incWriteLock(int giveupReadLocks) throws AssertionError {
+ DebugLockInfo info = getLockInfo(this.fLockDebugging);
+ if (info.fReadLocks != giveupReadLocks) {
+ outputReadLocks(this.fLockDebugging);
+ throw new AssertionError("write lock with " + giveupReadLocks + " readlocks, expected " + info.fReadLocks);
+ }
+ if (info.fWriteLocks != 0)
+ throw new AssertionError("Duplicate write lock");
+ info.fWriteLocks++;
+ }
+
+ // For debugging lock issues
+ private void decWriteLock(int establishReadLocks) throws AssertionError {
+ DebugLockInfo info = getLockInfo(this.fLockDebugging);
+ if (info.fReadLocks != establishReadLocks)
+ throw new AssertionError("release write lock with " + establishReadLocks + " readlocks, expected " + info.fReadLocks); //$NON-NLS-1$ //$NON-NLS-2$
+ if (info.fWriteLocks != 1)
+ throw new AssertionError("Wrong release write lock"); //$NON-NLS-1$
+ info.fWriteLocks= 0;
+ if (info.fReadLocks == 0) {
+ this.fLockDebugging.remove(Thread.currentThread());
+ }
+ }
+
+ // For debugging lock issues
+ @SuppressWarnings("nls")
+ private long reportBlockedWriteLock(long start, int giveupReadLocks) {
+ long now= System.currentTimeMillis();
+ if (now >= start + BLOCKED_WRITE_LOCK_OUTPUT_INTERVAL) {
+ System.out.println();
+ System.out.println("Blocked writeLock");
+ System.out.println(" lockcount= " + this.lockCount + ", giveupReadLocks=" + giveupReadLocks + ", waitingReaders=" + this.waitingReaders);
+ outputReadLocks(this.fLockDebugging);
+ start= now;
+ }
+ return start;
+ }
+
+ // For debugging lock issues
+ @SuppressWarnings("nls")
+ private static void outputReadLocks(Map<Thread, DebugLockInfo> lockDebugging) {
+ System.out.println("--------------------- Lock Debugging -------------------------");
+ for (Thread th: lockDebugging.keySet()) {
+ DebugLockInfo info = lockDebugging.get(th);
+ info.write(th.getName());
+ }
+ System.out.println("---------------------------------------------------------------");
+ }
+
+ // For debugging lock issues
+ public void adjustThreadForReadLock(Map<Thread, DebugLockInfo> lockDebugging) {
+ for (Thread th : lockDebugging.keySet()) {
+ DebugLockInfo val= lockDebugging.get(th);
+ if (val.fReadLocks > 0) {
+ DebugLockInfo myval= this.fLockDebugging.get(th);
+ if (myval == null) {
+ myval= new DebugLockInfo();
+ this.fLockDebugging.put(th, myval);
+ }
+ myval.inc(val);
+ for (int i = 0; i < val.fReadLocks; i++) {
+ decReadLock(this.fLockDebugging);
+ }
+ }
+ }
+ }
+
+ public NdNode getNode(long address, short nodeType) throws IndexException {
+ return this.fNodeTypeRegistry.createNode(this, address, nodeType);
+ }
+
+ public <T extends NdNode> ITypeFactory<T> getTypeFactory(short nodeType) {
+ return this.fNodeTypeRegistry.getTypeFactory(nodeType);
+ }
+
+ /**
+ * Returns the type ID for the given class
+ */
+ public short getNodeType(Class<? extends NdNode> toQuery) {
+ return this.fNodeTypeRegistry.getTypeForClass(toQuery);
+ }
+
+ private void deleteIfUnreferenced(long address) {
+ if (address == 0) {
+ return;
+ }
+ short nodeType = NdNode.NODE_TYPE.get(this, address);
+
+ // Look up the type
+ ITypeFactory<? extends NdNode> factory1 = getTypeFactory(nodeType);
+
+ if (factory1.isReadyForDeletion(this, address)) {
+ // Call its destructor
+ factory1.destruct(this, address);
+
+ // Free up its memory
+ getDB().free(address, (short)(Database.POOL_FIRST_NODE_TYPE + nodeType));
+ }
+ }
+
+ public void delete(long address) {
+ if (address == 0) {
+ return;
+ }
+ short nodeType = NdNode.NODE_TYPE.get(this, address);
+
+ // Look up the type
+ ITypeFactory<? extends NdNode> factory1 = getTypeFactory(nodeType);
+
+ // Call its destructor
+ factory1.destruct(this, address);
+
+ // Free up its memory
+ getDB().free(address, (short)(Database.POOL_FIRST_NODE_TYPE + nodeType));
+
+ // If this node was in the list of pending deletions, remove it since it's now been deleted
+ if (this.pendingDeletions.containsKey(address)) {
+ logDoubleDeletion(address);
+ this.pendingDeletions.remove(address);
+ }
+ }
+
+ public NdNodeTypeRegistry<NdNode> getTypeRegistry() {
+ return this.fNodeTypeRegistry;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdLinkedList.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdLinkedList.java
new file mode 100644
index 000000000..cfce2080e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdLinkedList.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+public final class NdLinkedList<T> {
+ private final NdRawLinkedList rawList;
+ final ITypeFactory<T> elementFactory;
+
+ public static interface ILinkedListVisitor<T> {
+ public void visit(T record, short metadataBits, int index) throws IndexException;
+ }
+
+ public NdLinkedList(Nd nd, long address, ITypeFactory<T> elementFactory, int recordsInFirstBlock,
+ int recordsInSubsequentBlocks) {
+ this(nd, address, elementFactory, recordsInFirstBlock, recordsInSubsequentBlocks, 0);
+ }
+
+ public NdLinkedList(Nd nd, long address, ITypeFactory<T> elementFactory, int recordsInFirstBlock,
+ int recordsInSubsequentBlocks, int metadataBitsPerElement) {
+ this.rawList = new NdRawLinkedList(nd, address, elementFactory.getRecordSize(), recordsInFirstBlock,
+ recordsInSubsequentBlocks, metadataBitsPerElement);
+ this.elementFactory = elementFactory;
+ }
+
+ /**
+ * Computes the size of this list. This is an O(n) operation.
+ *
+ * @return the size of this list
+ * @throws IndexException
+ */
+ public int size() throws IndexException {
+ return this.rawList.size();
+ }
+
+ public T addMember(short metadataBits) throws IndexException {
+ long address = this.rawList.addMember(metadataBits);
+
+ return this.elementFactory.create(this.rawList.getNd(), address);
+ }
+
+ public void accept(final ILinkedListVisitor<T> visitor) throws IndexException {
+ final NdRawLinkedList localRawList = this.rawList;
+ final ITypeFactory<T> localElementFactory = this.elementFactory;
+ localRawList.accept(new NdRawLinkedList.ILinkedListVisitor() {
+ @Override
+ public void visit(long address, short metadataBits, int index) throws IndexException {
+ visitor.visit(localElementFactory.create(localRawList.getNd(),
+ address), metadataBits, index);
+ }
+ });
+ }
+
+ public static <T> ITypeFactory<NdLinkedList<T>> getFactoryFor(
+ final ITypeFactory<T> elementFactory, final int recordsInFirstBlock, final int recordsInSubsequentBlocks) {
+ return getFactoryFor(elementFactory, recordsInSubsequentBlocks, 0);
+ }
+
+ public static <T> ITypeFactory<NdLinkedList<T>> getFactoryFor(
+ final ITypeFactory<T> elementFactory, final int recordsInFirstBlock, final int recordsInSubsequentBlocks,
+ final int metadataBitsPerElement) {
+
+ return new AbstractTypeFactory<NdLinkedList<T>>() {
+ public NdLinkedList<T> create(Nd dom, long address) {
+ return new NdLinkedList<T>(dom, address, elementFactory, recordsInFirstBlock, recordsInSubsequentBlocks, metadataBitsPerElement);
+ }
+
+ @Override
+ public int getRecordSize() {
+ return NdRawLinkedList.recordSize(elementFactory.getRecordSize(), recordsInFirstBlock,
+ metadataBitsPerElement);
+ }
+
+ @Override
+ public Class<?> getElementClass() {
+ return NdLinkedList.class;
+ }
+
+ @Override
+ public boolean hasDestructor() {
+ return true;
+ }
+
+ @Override
+ public void destructFields(Nd dom, long address) {
+ create(dom, address).destruct();
+ }
+
+ @Override
+ public void destruct(Nd dom, long address) {
+ destructFields(dom, address);
+ }
+ };
+ }
+
+ /**
+ *
+ */
+ protected void destruct() {
+ if (this.elementFactory.hasDestructor()) {
+ final Nd nd = this.rawList.getNd();
+ this.rawList.accept(new NdRawLinkedList.ILinkedListVisitor() {
+ @Override
+ public void visit(long address, short metadataBits, int index) throws IndexException {
+ NdLinkedList.this.elementFactory.destruct(nd, address);
+ }
+ });
+ }
+ this.rawList.destruct();
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
new file mode 100644
index 000000000..67d039b35
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNode.java
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldShort;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * This is a basic node in the network database.
+ */
+public abstract class NdNode implements IDestructable {
+ public static final FieldShort NODE_TYPE;
+
+ public static final StructDef<NdNode> type;
+
+ static {
+ type = StructDef.create(NdNode.class);
+ NODE_TYPE = type.addShort();
+ type.done();
+ }
+
+ public final long address;
+ private Nd nd;
+
+ public static long addressOf(NdNode nullable) {
+ if (nullable == null) {
+ return 0;
+ }
+ return nullable.address;
+ }
+
+ /**
+ * Load a node from the specified address in the given database. Return null if a node cannot
+ * be loaded.
+ *
+ * @param nd The {@link Nd} from which to load the node.
+ * @param address The address of the node in the given {@link Nd}.
+ * @return The {@link NdNode} at the specified location or null if a node cannot be loaded.
+ * @When there is a problem reading the given {@link Nd}'s Database
+ */
+ public static NdNode load(Nd nd, long address) {
+ if (address == 0) {
+ return null;
+ }
+
+ return nd.getNode(address, NODE_TYPE.get(nd, address));
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T extends NdNode> T load(Nd nd, long address, Class<T> clazz) {
+ if (address == 0) {
+ return null;
+ }
+
+ NdNode result = nd.getNode(address, NODE_TYPE.get(nd, address));
+
+ if (!clazz.isAssignableFrom(result.getClass())) {
+ throw new IndexException("Found wrong data type at address " + address + ". Expected a subclass of " + //$NON-NLS-1$//$NON-NLS-2$
+ clazz + " but found " + result.getClass()); //$NON-NLS-1$
+ }
+
+ return (T)result;
+ }
+
+ /**
+ * Invokes the destructor on this node and frees up its memory
+ */
+ public final void delete() {
+ getNd().delete(this.address);
+ }
+
+ protected NdNode(Nd nd, long address) {
+ this.nd = nd;
+ this.address = address;
+ }
+
+ protected NdNode(Nd nd) {
+ Database db = nd.getDB();
+ this.nd = nd;
+
+ short nodeType = nd.getNodeType(getClass());
+ ITypeFactory<? extends NdNode> factory1 = nd.getTypeFactory(nodeType);
+
+ this.address = db.malloc(factory1.getRecordSize(), (short)(Database.POOL_FIRST_NODE_TYPE + nodeType));
+
+ NODE_TYPE.put(nd, this.address, nodeType);
+ }
+
+ protected Database getDB() {
+ return this.nd.getDB();
+ }
+
+ public Nd getNd() {
+ return this.nd;
+ }
+
+ /**
+ * Return a value to uniquely identify the node within the factory that is responsible for loading
+ * instances of this node from the {@link Nd}.
+ * <b>
+ */
+ public short getNodeType() {
+ return this.nd.getNodeType(getClass());
+ }
+
+ public final long getAddress() {
+ return this.address;
+ }
+
+ public final long getBindingID() {
+ return this.address;
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj instanceof NdNode) {
+ NdNode other = (NdNode) obj;
+ return getNd() == other.getNd() && this.address == other.address;
+ }
+
+ return super.equals(obj);
+ }
+
+ @Override
+ public final int hashCode() {
+ return (int) (this.address >> Database.BLOCK_SIZE_DELTA_BITS);
+ }
+
+ public void accept(INdVisitor visitor) {
+ // No children here.
+ }
+
+ /**
+ * Return an value to globally identify the given node within the given linkage. This value
+ * can be used for comparison with other {@link NdNode}s.
+ */
+ public static int getNodeId(int linkageID, int nodeType) {
+ return (linkageID << 16) | (nodeType & 0xffff);
+ }
+
+ /**
+ * Convenience method for fetching a byte from the database.
+ * @param offset Location of the byte.
+ * @return a byte from the database.
+ */
+ protected byte getByte(long offset) {
+ return getDB().getByte(offset);
+ }
+
+ /**
+ * Returns the bit at the specified offset in a bit vector.
+ * @param bitVector Bits.
+ * @param offset The position of the desired bit.
+ * @return the bit at the specified offset.
+ */
+ protected static boolean getBit(int bitVector, int offset) {
+ int mask = 1 << offset;
+ return (bitVector & mask) != 0;
+ }
+
+ /**
+ * Dispose this {@link NdNode}. Subclasses should extend this method to perform any high-level node-specific cleanup.
+ * This will be invoked prior to disposing the fields. Implementations must invoke their parent's destruct method
+ * and should not destruct the fields.
+ * <p>
+ * If an external object wants to destroy a node, they should invoke {@link NdNode#delete} rather than this
+ * method.
+ */
+ public void destruct() {
+ // Nothing to do by default. Subclasses will provide an implementation if necessary.
+ }
+
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java
new file mode 100644
index 000000000..b76057dd5
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdNodeTypeRegistry.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * Maps integer constants onto factories for {@link NdNode} objects.
+ */
+public class NdNodeTypeRegistry<R> {
+ private final Map<Short, ITypeFactory<? extends R>> types = new HashMap<>();
+ private final BitSet reserved = new BitSet();
+ private final Map<Class<?>, Short> registeredClasses = new HashMap<>();
+
+ /**
+ * Registers a class to be used with this node type registry. Note that if we ever want to stop registering a type
+ * name in the future, its fully-qualified class name should be passed to reserve(...) to prevent its hashfrom being
+ * reused in the future.
+ */
+ public <T extends R> void register(int typeId, ITypeFactory<T> toRegister) {
+ if ((typeId & 0xFFFF0000) != 0) {
+ throw new IllegalArgumentException("The typeId " + typeId + " does not fit within a short int"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ short shortTypeId = (short)typeId;
+ String fullyQualifiedClassName = toRegister.getElementClass().getName();
+
+ if (this.types.containsKey(typeId) || this.reserved.get(typeId)) {
+ throw new IllegalArgumentException(
+ "The type id " + typeId + " for class " + fullyQualifiedClassName + " is already in use."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ this.types.put(shortTypeId, toRegister);
+ this.registeredClasses.put(toRegister.getElementClass(), shortTypeId);
+ }
+
+ /**
+ * Reserves the given node class name, such that its hash cannot be used by any other node registered with
+ * "register". If we ever want to unregister a given Class from the type registry, its class name should be reserved
+ * using this method. Doing so will prevent its type ID from being reused by another future class.
+ */
+ public void reserve(short typeId) {
+ if (this.types.containsKey(typeId) || this.reserved.get(typeId)) {
+ throw new IllegalArgumentException("The type ID " + typeId + " is already in use"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ this.reserved.set(typeId);
+ }
+
+ /**
+ * Returns the class associated with the given type or null if the given type ID is not known
+ */
+ public ITypeFactory<? extends R> getClassForType(short type) {
+ return this.types.get(type);
+ }
+
+ public R createNode(Nd nd, long address, short nodeType) throws IndexException {
+ ITypeFactory<? extends R> typeFactory = this.types.get(nodeType);
+
+ return typeFactory.create(nd, address);
+ }
+
+ public short getTypeForClass(Class<? extends R> toQuery) {
+ Short classId = this.registeredClasses.get(toQuery);
+
+ if (classId == null) {
+ throw new IllegalArgumentException(toQuery.getName() + " was not registered as a node type"); //$NON-NLS-1$
+ }
+ return classId;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends R> ITypeFactory<T> getTypeFactory(short nodeType) {
+ ITypeFactory<T> result = (ITypeFactory<T>) this.types.get(nodeType);
+
+ if (result == null) {
+ throw new IllegalArgumentException("The node type " + nodeType //$NON-NLS-1$
+ + " is not registered with this database"); //$NON-NLS-1$
+ }
+
+ return result;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdRawLinkedList.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdRawLinkedList.java
new file mode 100644
index 000000000..969a893c0
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/NdRawLinkedList.java
@@ -0,0 +1,271 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * {@link NdRawLinkedList} stores a list of fixed-sized records. Along with the records themselves, there is also
+ * a bit field associated with each record which can hold a small number of bits of metadata per record.
+ * The underlying format is as follows:
+ *
+ * <pre>
+ * Bytes Content
+ * ----------------
+ * 4 Pointer to the next block. If this is 0, this is the last block and it is not yet full. The number of
+ * elements will be stored at the position where the last element would normally start. If this points back
+ * to the start of the block, this is the last block and it is full. If this holds any other value, the
+ * block is full and this points to the next block.
+ * headerSize Bit field for this block (the bits for each element are tightly packed)
+ * recordSize The content of the first element in the block
+ * recordSize The content of the second element in the block
+ * ... repeated recordsPerBlock times
+ * recordSize If the block is full, this holds the last
+ * </pre>
+ *
+ * stored in linked blocks where each block is an array of record pointers. Each block contains a pointer to the
+ * subsequent block, so they can be chained.
+ * <p>
+ * The size of the blocks are generally hardcoded. All blocks are the same size except for the first block whose size
+ * may be configured independently. The size of the first block may be zero, in which case the first "block" is
+ * simply a pointer to the following block or null.
+ */
+public class NdRawLinkedList {
+ private static final int NEXT_MEMBER_BLOCK = 0;
+ private static final int ELEMENT_START_POSITION = NEXT_MEMBER_BLOCK + Database.PTR_SIZE;
+
+ private final long address;
+ private final Nd nd;
+ private final int firstBlockRecordCount;
+ private final int recordCount;
+ private final int elementRecordSize;
+ private final int metadataBitsPerRecord;
+
+ // Derived data. Holds the address for the last block we know about
+ private long lastKnownBlock;
+
+ public static interface ILinkedListVisitor {
+ public void visit(long address, short metadataBits, int index) throws IndexException;
+ }
+
+ /**
+ * @param nd the Nd object
+ * @param address pointer to the start of the linked list
+ * @param recordsPerBlock number of records per block. This is normally a hardcoded value.
+ */
+ public NdRawLinkedList(Nd nd, long address, int elementRecordSize, int firstBlockRecordCount, int recordsPerBlock,
+ int metadataBitsPerRecord) {
+ assert(recordsPerBlock > 0);
+ assert(firstBlockRecordCount >= 0);
+ this.nd = nd;
+ this.address = address;
+ this.firstBlockRecordCount = firstBlockRecordCount;
+ this.recordCount = recordsPerBlock;
+ this.elementRecordSize = elementRecordSize;
+ this.lastKnownBlock = address;
+ this.metadataBitsPerRecord = metadataBitsPerRecord;
+ }
+
+ /**
+ * Returns the record size for a linked list with the given element record size and number of
+ * records per block
+ */
+ public static int recordSize(int elementRecordSize, int recordsPerBlock, int metadataBitsPerRecord) {
+ int metadataSize = 0;
+
+ if (metadataBitsPerRecord > 0) {
+ int metadataRecordsPerShort = 16 / metadataBitsPerRecord;
+ int numberOfShorts = (recordsPerBlock + metadataRecordsPerShort - 1) / metadataRecordsPerShort;
+
+ metadataSize = 2 * numberOfShorts;
+ }
+
+ return Database.PTR_SIZE + elementRecordSize * recordsPerBlock + metadataSize;
+ }
+
+ public Nd getNd() {
+ return this.nd;
+ }
+
+ private int getElementsInBlock(long currentRecord, long ptr, int currentRecordCount) throws IndexException {
+ if (ptr == 0 && currentRecordCount > 0) {
+ return getDB().getInt(getAddressOfElement(currentRecord, currentRecordCount - 1));
+ }
+ return currentRecordCount;
+ }
+
+ private Database getDB() {
+ return this.nd.getDB();
+ }
+
+ public long getAddress() {
+ return this.address;
+ }
+
+ /**
+ * Adds a new element to the list and returns the record pointer to the start of the newly-allocated object
+ *
+ * @param metadataBits the metadata bits to attach to the new member. Use 0 if this list does not use metadata.
+ */
+ public long addMember(short metadataBits) throws IndexException {
+ Database db = getDB();
+ long current = this.lastKnownBlock;
+ int thisBlockRecordCount = this.firstBlockRecordCount;
+ while (true) {
+ long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+ int elementsInBlock = getElementsInBlock(current, ptr, thisBlockRecordCount);
+
+ // If there's room in this block
+ if (elementsInBlock < thisBlockRecordCount) {
+ long positionOfElementCount = getAddressOfElement(current, thisBlockRecordCount - 1);
+ // If there's only one space left
+ if (elementsInBlock == thisBlockRecordCount - 1) {
+ // We use the fact that the next pointer points to itself as a sentinel to indicate that the
+ // block is full and there are no further blocks
+ db.putRecPtr(current + NEXT_MEMBER_BLOCK, current);
+ // Zero out the int we've been using to hold the count of elements
+ db.putInt(positionOfElementCount, 0);
+ } else {
+ // Increment the element count
+ db.putInt(positionOfElementCount, elementsInBlock + 1);
+ }
+
+ if (this.metadataBitsPerRecord > 0) {
+ int metadataMask = (1 << this.metadataBitsPerRecord) - 1;
+ int metadataRecordsPerShort = this.metadataBitsPerRecord == 0 ? 0
+ : (16 / this.metadataBitsPerRecord);
+ metadataBits &= metadataMask;
+
+ int metadataBitOffset = elementsInBlock % metadataRecordsPerShort;
+ long metadataStart = getAddressOfMetadata(current, thisBlockRecordCount);
+ int whichShort = elementsInBlock / metadataRecordsPerShort;
+ long metadataOffset = metadataStart + 2 * whichShort;
+ short metadataValue = db.getShort(metadataOffset);
+
+ // Resetting the previous visibility bits of the target member.
+ metadataValue &= ~(metadataMask << metadataBitOffset * this.metadataBitsPerRecord);
+ // Setting the new visibility bits of the target member.
+ metadataValue |= metadataBits << metadataBitOffset * this.metadataBitsPerRecord;
+
+ getDB().putShort(metadataOffset, metadataValue);
+ }
+
+ this.lastKnownBlock = current;
+ return getAddressOfElement(current, elementsInBlock);
+ } else {
+ // When ptr == current, this is a sentinel indicating that the block is full and there are no
+ // further blocks. If this is the case, create a new block
+ if (isLastBlock(current, ptr)) {
+ current = db.malloc(
+ recordSize(this.elementRecordSize, this.recordCount, this.metadataBitsPerRecord), Database.POOL_LINKED_LIST);
+ db.putRecPtr(current + NEXT_MEMBER_BLOCK, current);
+ } else {
+ thisBlockRecordCount = this.recordCount;
+ // Else, there are more blocks following this one so advance
+ current = ptr;
+ }
+ }
+ }
+ }
+
+ private long getAddressOfElement(long blockRecordStart, int elementNumber) {
+ return blockRecordStart + ELEMENT_START_POSITION + elementNumber * this.elementRecordSize;
+ }
+
+ private long getAddressOfMetadata(long blockRecordStart, int blockRecordCount) {
+ return getAddressOfElement(blockRecordStart, blockRecordCount);
+ }
+
+ public void accept(ILinkedListVisitor visitor) throws IndexException {
+ int count = 0;
+ Database db = getDB();
+
+ int blockRecordCount = this.firstBlockRecordCount;
+ int metadataMask = (1 << this.metadataBitsPerRecord) - 1;
+ int metadataRecordsPerShort = this.metadataBitsPerRecord == 0 ? 0 : (16 / this.metadataBitsPerRecord);
+ long current = this.address;
+ while (true) {
+ long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+ int elementsInBlock = getElementsInBlock(current, ptr, blockRecordCount);
+
+ long metadataStart = getAddressOfMetadata(current, blockRecordCount);
+ for (int idx = 0; idx < elementsInBlock; idx++) {
+ long elementRecord = getAddressOfElement(current, idx);
+
+ short metadataBits = 0;
+
+ if (metadataRecordsPerShort > 0) {
+ int metadataBitOffset = idx % metadataRecordsPerShort;
+ int whichShort = idx / metadataRecordsPerShort;
+ long metadataOffset = metadataStart + 2 * whichShort;
+ metadataBits = getDB().getShort(metadataOffset);
+
+ metadataBits >>>= metadataBits * metadataBitOffset;
+ metadataBits &= metadataMask;
+ }
+
+ visitor.visit(elementRecord, metadataBits, count++);
+ }
+
+ blockRecordCount = this.recordCount;
+
+ if (isLastBlock(current, ptr)) {
+ return;
+ }
+
+ current = ptr;
+ }
+ }
+
+ public void destruct() throws IndexException {
+ Database db = getDB();
+ long current = this.address;
+ while (true) {
+ long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+ db.free(current, Database.POOL_LINKED_LIST);
+
+ if (isLastBlock(current, ptr)) {
+ return;
+ }
+
+ current = ptr;
+ }
+ }
+
+ private boolean isLastBlock(long blockAddress, long pointerToNextBlock) {
+ return pointerToNextBlock == 0 || pointerToNextBlock == blockAddress;
+ }
+
+ /**
+ * Returns the number of elements in this list. This is an O(n) operation.
+ * @throws IndexException
+ */
+ public int size() throws IndexException {
+ int count = 0;
+ Database db = getDB();
+ int currentRecordCount = this.firstBlockRecordCount;
+ long current = this.address;
+ while (true) {
+ long ptr = db.getRecPtr(current + NEXT_MEMBER_BLOCK);
+ count += getElementsInBlock(current, ptr, currentRecordCount);
+
+ if (isLastBlock(current, ptr)) {
+ break;
+ }
+
+ currentRecordCount = this.recordCount;
+ current = ptr;
+ }
+
+ return count;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Package.java
new file mode 100644
index 000000000..1782bd48c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Package.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/* package */ class Package {
+ public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+ public static void log(Throwable e) {
+ String msg = e.getMessage();
+ if (msg == null) {
+ log("Error", e); //$NON-NLS-1$
+ } else {
+ log("Error: " + msg, e); //$NON-NLS-1$
+ }
+ }
+
+ public static void log(String message, Throwable e) {
+ log(createStatus(message, e));
+ }
+
+ public static void logInfo(String message) {
+ log(createStatus(IStatus.INFO, message, null));
+ }
+
+ public static IStatus createStatus(int statusCode, String msg, Throwable e) {
+ return new Status(statusCode, PLUGIN_ID, msg, e);
+ }
+
+ public static IStatus createStatus(String msg, Throwable e) {
+ return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+ }
+
+ public static IStatus createStatus(String msg) {
+ return new Status(IStatus.ERROR, PLUGIN_ID, msg);
+ }
+
+ public static void log(IStatus status) {
+ JavaCore.getPlugin().getLog().log(status);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Pointer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Pointer.java
new file mode 100644
index 000000000..bd8a59783
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/Pointer.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Points to a concrete type, NOT one of its subclasses. This should not be used for node
+ * pointers, since they are stored as a pointer to the base class. If you want a pointer to
+ * a node, use a NodeFieldDefinition instead.
+ */
+public class Pointer<T> {
+ private final Nd nd;
+ private final long address;
+ private ITypeFactory<T> targetFactory;
+
+ public Pointer(Nd nd, long address, ITypeFactory<T> targetFactory) {
+ this.nd = nd;
+ this.address = address;
+ this.targetFactory = targetFactory;
+ }
+
+ public T get() {
+ long ptr = this.nd.getDB().getRecPtr(this.address);
+
+ if (ptr == 0) {
+ return null;
+ }
+
+ return this.targetFactory.create(this.nd, ptr);
+ }
+
+ public static <T> ITypeFactory<Pointer<T>> getFactory(final ITypeFactory<T> targetFactory) {
+ if (NdNode.class.isAssignableFrom(targetFactory.getElementClass())) {
+ throw new IllegalArgumentException("Don't use Pointer<T> for references to NdNode"); //$NON-NLS-1$
+ }
+ return new AbstractTypeFactory<Pointer<T>>() {
+ @Override
+ public Pointer<T> create(Nd dom, long address) {
+ return new Pointer<T>(dom, address, targetFactory);
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.PTR_SIZE;
+ }
+
+ @Override
+ public Class<?> getElementClass() {
+ return Pointer.class;
+ }
+ };
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/PrimitiveTypes.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/PrimitiveTypes.java
new file mode 100644
index 000000000..018506890
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/PrimitiveTypes.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Holds type factories for all primitive types known to the Database.
+ */
+public class PrimitiveTypes {
+ public static final ITypeFactory<Long> Pointer = new AbstractTypeFactory<Long>() {
+ @Override
+ public Long create(Nd dom, long address) {
+ return dom.getDB().getRecPtr(address);
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.PTR_SIZE;
+ }
+
+ @Override
+ public Class<?> getElementClass() {
+ return Long.class;
+ }
+ };
+
+ public static final ITypeFactory<Short> Short = new AbstractTypeFactory<Short>() {
+ @Override
+ public Short create(Nd dom, long address) {
+ return dom.getDB().getShort(address);
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.SHORT_SIZE;
+ }
+
+ @Override
+ public Class<?> getElementClass() {
+ return Short.class;
+ }
+ };
+
+ public static final ITypeFactory<Integer> Integer = new AbstractTypeFactory<Integer>() {
+ @Override
+ public Integer create(Nd dom, long address) {
+ return dom.getDB().getInt(address);
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.INT_SIZE;
+ }
+
+ @Override
+ public Class<?> getElementClass() {
+ return Integer.class;
+ }
+ };
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
new file mode 100644
index 000000000..df7ca4ee9
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/RawGrowableArray.java
@@ -0,0 +1,593 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldPointer;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Implements a growable array of pointers that supports constant-time insertions and removals. Items are inserted at
+ * the end of the array, and each insertion hands back a unique identifier that can be used to remove the item quickly
+ * at a later time.
+ * <p>
+ * The memory format contains a header is as follows:
+ * <p>
+ *
+ * <pre>
+ * Byte Meaning
+ * --------------------------------------------
+ * 0..3 Pointer to the growable block. Null if the number of records <= inlineRecordCount
+ * 4..7 Record [0]
+ * 8..11 Record [1]
+ * ...
+ * k...k+4 Record [inlineRecordCount-1]
+ * </pre>
+ *
+ * As shown above, the first few records are stored inline with the array. inlineRecordCount is a tunable parameter
+ * which may be 0. If there are fewer than inlineRecordCount records, there is no growable block and all records are
+ * stored in the header. Storing the first few records in the header is intended as an optimization for very small
+ * arrays in the case where small arrays are expected to be a common case. If there are fewer than inlineRecordCount
+ * records stored in the array, the size of the array is not stored explicitly. It is computed on demand by searching
+ * for the first null entry among the inline records.
+ *
+ * <p>
+ * The memory format for a growable block is as follows:
+ * <p>
+ *
+ * <pre>
+ * Byte Meaning
+ * --------------------------------------------
+ * 0..3 Size of the array, including all inline records. This is also the index at which the next entry will
+ * be inserted
+ * 4..7 Capacity of this growable block.
+ * 8..11 Record [n]
+ * 12..15 Record [n+1]
+ * ...
+ * k...k+4 Record [blockSize-1]
+ * </pre>
+ *
+ * <p>
+ * The growable block itself begins with a 4-byte int holding the size of the array, followed by a 4-byte int holding
+ * the capacity of the growable block. In the event that the array is larger than
+ * {@link GrowableBlockHeader#MAX_GROWABLE_SIZE} enough to be using a metablock, there will be multiple growable blocks
+ * in use. In this case, the size and capacity stored in the metablock is used for the array and the size and capacity
+ * stored in each growable block will be filled in with 0s.
+ * <p>
+ * If capacity <= MAX_BLOCK_SIZE then this is a normal block containing a flat array of record pointers starting from
+ * the element numbered inlineRecordCount. If capacity > MAX_BLOCK_SIZE then then it is a metablock which holds record
+ * pointers to separate growable blocks, each of which holds exactly MAX_BLOCK_SIZE elements.
+ * <p>
+ * Every time an element is inserted in the array, the add method returns the element's index. Indices can be used to
+ * remove elements in constant time, but will be reassigned by the RawGrowableArray during removes by swapping the
+ * removed element with the last element in the array. If the owner of the array is keeping track of indices, it should
+ * update the relevant indices on remove.
+ * <p>
+ * The array itself is tightly packed. When an element is removed, the last element in the array is swapped into its
+ * location. Anyone keeping track of indices may rely on the fact that they are consecutive integers.
+ * <p>
+ * These arrays preserve insertion order until the first call to "remove". If element order matters, you should not
+ * remove individual elements but should instead destroy and rebuild the entire array.
+ * <p>
+ * Element additions and removals run in constant amortized time.
+ * <p>
+ * There are a lot of ints and longs used in the implementation of this class. In order to help clarify their function,
+ * they get the following suffixes:
+ * <ul>
+ * <li>index - holds an index into the array
+ * <li>size - holds a count of the number of indices
+ * <li>value - holds one of the pointer values inserted into the array
+ * <li>address - holds a pointer into the database (refers to a full 8-byte long, not the compressed 4-byte version)
+ * <li>bytes - holds the size (in bytes) of something in the database
+ * <li>block - holds a block number (in the case where a metablock is in use, the growable blocks are identified by
+ * block numbers).
+ * <li>blockCount - holds a number of blocks
+ * </ul>
+ */
+public final class RawGrowableArray {
+ private static final FieldPointer GROWABLE_BLOCK_ADDRESS;
+ private static final int ARRAY_HEADER_BYTES;
+
+ private static final StructDef<RawGrowableArray> type;
+
+ static {
+ type = StructDef.createAbstract(RawGrowableArray.class);
+ GROWABLE_BLOCK_ADDRESS = type.addPointer();
+ type.done();
+
+ ARRAY_HEADER_BYTES = type.size();
+ }
+
+ private static final class GrowableBlockHeader {
+ public static final FieldInt ARRAY_SIZE;
+ public static final FieldInt ALLOCATED_SIZE;
+ public static final int GROWABLE_BLOCK_HEADER_BYTES;
+ public static final int MAX_GROWABLE_SIZE;
+
+ @SuppressWarnings("hiding")
+ private static final StructDef<GrowableBlockHeader> type;
+
+ static {
+ type = StructDef.createAbstract(GrowableBlockHeader.class);
+
+ ARRAY_SIZE = type.addInt();
+ ALLOCATED_SIZE = type.addInt();
+ type.done();
+
+ GROWABLE_BLOCK_HEADER_BYTES = type.size();
+
+ MAX_GROWABLE_SIZE = (Database.MAX_MALLOC_SIZE - GROWABLE_BLOCK_HEADER_BYTES)
+ / Database.PTR_SIZE;
+ }
+ }
+
+ private final int inlineSize;
+
+ public RawGrowableArray(int inlineRecords) {
+ this.inlineSize = inlineRecords;
+ }
+
+ public static int getMaxGrowableBlockSize() {
+ return GrowableBlockHeader.MAX_GROWABLE_SIZE;
+ }
+
+ /**
+ * Returns the size of the array.
+ *
+ * @param address address of the array
+ * @return the array size, in number elements
+ */
+ public int size(Nd nd, long address) {
+ Database db = nd.getDB();
+ long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+ if (growableBlockAddress == 0) {
+ // If there is no growable block or metablock, then the size is determined by the position of the first
+ // null pointer among the inline records.
+ long inlineRecordStartAddress = address + ARRAY_HEADER_BYTES;
+ for (int index = 0; index < this.inlineSize; index++) {
+ long nextAddress = inlineRecordStartAddress + index * Database.PTR_SIZE;
+
+ long nextValue = db.getRecPtr(nextAddress);
+ if (nextValue == 0) {
+ return index;
+ }
+ }
+ return this.inlineSize;
+ }
+ return GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress);
+ }
+
+ /**
+ * Adds the given value to the array. Returns an index which can be later passed into remove in order to
+ * remove the element at a later time.
+ */
+ public int add(Nd nd, long address, long value) {
+ if (value == 0) {
+ throw new IllegalArgumentException("Null pointers cannot be inserted into " + getClass().getName()); //$NON-NLS-1$
+ }
+ Database db = nd.getDB();
+
+ int insertionIndex = size(nd, address);
+ int newSize = insertionIndex + 1;
+
+ ensureCapacity(nd, address, newSize);
+ long recordAddress = getAddressOfRecord(nd, address, insertionIndex);
+ db.putRecPtr(recordAddress, value);
+ setSize(nd, address, newSize);
+ return insertionIndex;
+ }
+
+ /**
+ * Returns the element at the given index (nonzero). The given index must be < size().
+ */
+ public long get(Nd nd, long address, int index) {
+ long recordAddress = getAddressOfRecord(nd, address, index);
+ return nd.getDB().getRecPtr(recordAddress);
+ }
+
+ /**
+ * Ensures that the array contains at least enough space allocated to fit the given number of new elements.
+ */
+ public void ensureCapacity(Nd nd, long address, int desiredSize) {
+ int growableBlockNeededSize = desiredSize - this.inlineSize;
+ long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+ int growableBlockCurrentSize = growableBlockAddress == 0 ? 0
+ : GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+
+ // The growable region is already large enough.
+ if (growableBlockNeededSize <= growableBlockCurrentSize) {
+ return;
+ }
+
+ Database db = nd.getDB();
+
+ int neededBlockSize = getGrowableRegionSizeFor(desiredSize);
+ if (neededBlockSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+ // We need a metablock.
+ long metablockAddress = growableBlockAddress;
+
+ if (!(growableBlockCurrentSize > GrowableBlockHeader.MAX_GROWABLE_SIZE)) {
+ // We weren't using a metablock previously
+ int currentSize = size(nd, address);
+ // Need to convert to using metablocks.
+ long firstGrowableBlockAddress = resizeBlock(nd, address, GrowableBlockHeader.MAX_GROWABLE_SIZE);
+
+ metablockAddress = db.malloc(computeBlockBytes(GrowableBlockHeader.MAX_GROWABLE_SIZE), Database.POOL_GROWABLE_ARRAY);
+ GrowableBlockHeader.ARRAY_SIZE.put(nd, metablockAddress, currentSize);
+ GrowableBlockHeader.ALLOCATED_SIZE.put(nd, metablockAddress,
+ GrowableBlockHeader.MAX_GROWABLE_SIZE);
+
+ // Link the first block into the metablock.
+ db.putRecPtr(metablockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES,
+ firstGrowableBlockAddress);
+ GROWABLE_BLOCK_ADDRESS.put(nd, address, metablockAddress);
+ }
+
+ // neededBlockSize should always be a multiple of the max block size when metablocks are in use
+ assert neededBlockSize % GrowableBlockHeader.MAX_GROWABLE_SIZE == 0;
+ // Create extra growable blocks if necessary.
+ int requiredBlockCount = neededBlockSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+ int currentAllocatedSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, metablockAddress);
+ assert currentAllocatedSize % GrowableBlockHeader.MAX_GROWABLE_SIZE == 0;
+ int currentBlockCount = currentAllocatedSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+
+ for (int nextBlock = currentBlockCount; nextBlock < requiredBlockCount; nextBlock++) {
+ long nextBlockAddress = db.malloc(computeBlockBytes(GrowableBlockHeader.MAX_GROWABLE_SIZE), Database.POOL_GROWABLE_ARRAY);
+
+ db.putRecPtr(metablockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES
+ + nextBlock * Database.PTR_SIZE, nextBlockAddress);
+ }
+
+ GrowableBlockHeader.ALLOCATED_SIZE.put(nd, metablockAddress, neededBlockSize);
+ } else {
+ long newBlockAddress = resizeBlock(nd, address, neededBlockSize);
+
+ GROWABLE_BLOCK_ADDRESS.put(nd, address, newBlockAddress);
+ }
+ }
+
+ /**
+ * Allocates a new normal block, copies the contents of the old block to it, and deletes the old block. Should not
+ * be used if the array is using metablocks. Returns the address of the newly-allocated block.
+ */
+ private long resizeBlock(Nd nd, long address, int newBlockSize) {
+ Database db = nd.getDB();
+ long oldGrowableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+ // Check if the existing block is already exactly the right size
+ if (oldGrowableBlockAddress != 0) {
+ if (newBlockSize == 0) {
+ db.free(oldGrowableBlockAddress, Database.POOL_GROWABLE_ARRAY);
+ return 0;
+ }
+
+ int oldAllocatedSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, oldGrowableBlockAddress);
+ if (oldAllocatedSize == newBlockSize) {
+ return oldGrowableBlockAddress;
+ }
+ }
+
+ int arraySize = size(nd, address);
+ int numToCopySize = Math.min(Math.max(0, arraySize - this.inlineSize), newBlockSize);
+ long newGrowableBlockAddress = db.malloc(computeBlockBytes(newBlockSize), Database.POOL_GROWABLE_ARRAY);
+
+ if (oldGrowableBlockAddress != 0) {
+ db.memcpy(newGrowableBlockAddress, oldGrowableBlockAddress, computeBlockBytes(numToCopySize));
+ db.free(oldGrowableBlockAddress, Database.POOL_GROWABLE_ARRAY);
+ }
+
+ GrowableBlockHeader.ARRAY_SIZE.put(nd, newGrowableBlockAddress, arraySize);
+ GrowableBlockHeader.ALLOCATED_SIZE.put(nd, newGrowableBlockAddress, newBlockSize);
+ return newGrowableBlockAddress;
+ }
+
+ private int computeBlockBytes(int size) {
+ return size * Database.PTR_SIZE + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+ }
+
+ /**
+ * @param size
+ */
+ private void setSize(Nd nd, long address, int size) {
+ long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+ // If we're not using a growable block, we don't explicitly store the size
+ if (growableBlockAddress == 0) {
+ return;
+ }
+
+ GrowableBlockHeader.ARRAY_SIZE.put(nd, growableBlockAddress, size);
+ }
+
+ /**
+ * Returns a record address given a record number
+ */
+ private long getAddressOfRecord(Nd nd, long address, int index) {
+ int growableBlockRelativeIndex = index - this.inlineSize;
+
+ if (growableBlockRelativeIndex >= 0) {
+ Database db = nd.getDB();
+ // This record is located within the growable region
+ long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+ int size = size(nd, address);
+
+ // We use reads of 1 past the end of the array to handle insertions.
+ if (index > size) {
+ throw new IndexException(
+ "Record index " + index + " out of range. Array contains " + size + " elements"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ int growableBlockSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+ long dataStartAddress = growableBlockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+
+ if (growableBlockSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+ // If this array is so big that it's using a metablock, look up the correct sub-block and use the
+ // correct address within the sub-block
+ int blockRelativeIndex = growableBlockRelativeIndex % GrowableBlockHeader.MAX_GROWABLE_SIZE;
+ int block = growableBlockRelativeIndex / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+
+ dataStartAddress = db.getRecPtr(dataStartAddress + block * Database.PTR_SIZE)
+ + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+ growableBlockRelativeIndex = blockRelativeIndex;
+ }
+
+ return dataStartAddress + growableBlockRelativeIndex * Database.PTR_SIZE;
+ } else {
+ // This record is one of the ones inlined in the header
+ return address + ARRAY_HEADER_BYTES + index * Database.PTR_SIZE;
+ }
+ }
+
+ /**
+ * Removes an entry from the array, given an element index. If the given index is not the last element
+ * in the list, the last element will have its index swapped with the removed element. If another element
+ * was swapped into the position of the removed element, this returns the value of that element. Otherwise,
+ * it returns 0.
+ */
+ public long remove(Nd nd, long address, int index) {
+ int currentSize = size(nd, address);
+ int lastElementIndex = currentSize - 1;
+
+ Database db = nd.getDB();
+ if (index > lastElementIndex || index < 0) {
+ throw new IndexException("Attempt to remove nonexistent element " + index //$NON-NLS-1$
+ + " from an array of size " + (lastElementIndex + 1)); //$NON-NLS-1$
+ }
+
+ long toRemoveAddress = getAddressOfRecord(nd, address, index);
+ long returnValue;
+ // If we're removing the last element
+ if (index == lastElementIndex) {
+ returnValue = 0;
+ // Clear out the removed element
+ db.putRecPtr(toRemoveAddress, 0);
+ } else {
+ long lastElementAddress = getAddressOfRecord(nd, address, lastElementIndex);
+ long lastElementValue = db.getRecPtr(lastElementAddress);
+
+ // Move the last element into the position occupied by the element being removed (this is a noop if
+ // removing the last element)
+ db.putRecPtr(toRemoveAddress, lastElementValue);
+
+ // Clear out the last element
+ db.putRecPtr(lastElementAddress, 0);
+
+ returnValue = lastElementValue;
+ }
+
+ // Update the array size
+ setSize(nd, address, currentSize - 1);
+ repackIfNecessary(nd, address, currentSize);
+
+ return returnValue;
+ }
+
+ /**
+ * Checks if we should reduce the amount of allocated in the growable region, such that the array can hold the given
+ * number of elements.
+ *
+ * @param desiredSize
+ * the new current size of the array or 0 to free up all memory
+ */
+ private void repackIfNecessary(Nd nd, long address, int desiredSize) {
+ long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+ // If there is no growable block then the array is already as small as we can make it. Nothing to do.
+ if (growableBlockAddress == 0) {
+ return;
+ }
+
+ int desiredGrowableSize = desiredSize - this.inlineSize;
+
+ int currentGrowableSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+ int newGrowableSize = getGrowableRegionSizeFor(desiredSize);
+
+ // We only need to repack if the new size is smaller than the old one
+ if (newGrowableSize >= currentGrowableSize) {
+ return;
+ }
+
+ Database db = nd.getDB();
+ if (currentGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+ // We are currently using a metablock
+ int desiredBlockCount = (newGrowableSize + GrowableBlockHeader.MAX_GROWABLE_SIZE - 1)
+ / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+ int currentBlockCount = currentGrowableSize / GrowableBlockHeader.MAX_GROWABLE_SIZE;
+
+ // Only deallocate memory if either there are either two full unused blocks
+ // or the desired size is less than or equal to half of a block + 1. We add one to ensure
+ // that the newly-shrunk array will still be about double the size of the used elements.
+ boolean needsRepacking = (currentBlockCount - desiredBlockCount > 1)
+ || (newGrowableSize <= (GrowableBlockHeader.MAX_GROWABLE_SIZE / 2 + 1));
+ if (!needsRepacking) {
+ return;
+ }
+
+ long metablockRecordsAddress = growableBlockAddress + GrowableBlockHeader.GROWABLE_BLOCK_HEADER_BYTES;
+ int currentBlock = currentBlockCount;
+ while (--currentBlock >= desiredBlockCount) {
+ long nextAddress = metablockRecordsAddress + currentBlock * Database.PTR_SIZE;
+ long oldBlockAddress = db.getRecPtr(nextAddress);
+ db.free(oldBlockAddress, Database.POOL_GROWABLE_ARRAY);
+ db.putRecPtr(nextAddress, 0);
+ }
+
+ // If we still need to be using a metablock, we're done
+ if (newGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+ // First record the new growable region size
+ GrowableBlockHeader.ALLOCATED_SIZE.put(nd, growableBlockAddress, newGrowableSize);
+ return;
+ }
+
+ // Else we need to stop using a metablock.
+ // Dispose the metablock and replace it with the first growable block
+ long firstBlockAddress = db.getRecPtr(metablockRecordsAddress);
+ int oldSize = GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress);
+ db.free(growableBlockAddress, Database.POOL_GROWABLE_ARRAY);
+
+ GROWABLE_BLOCK_ADDRESS.put(nd, address, firstBlockAddress);
+
+ if (firstBlockAddress != 0) {
+ currentGrowableSize = GrowableBlockHeader.MAX_GROWABLE_SIZE;
+ GrowableBlockHeader.ARRAY_SIZE.put(nd, firstBlockAddress, oldSize);
+ GrowableBlockHeader.ALLOCATED_SIZE.put(nd, firstBlockAddress,
+ GrowableBlockHeader.MAX_GROWABLE_SIZE);
+ }
+
+ // Then we'll fall through to the normal (non-metablock) case, which may shrink the size of the last
+ // growable block further
+ }
+
+ // If we're not using metablocks, we only resize the growable region once the size of the array shrinks
+ // such that we're only using 1/4 of it.
+ if (desiredGrowableSize <= (currentGrowableSize / 4 + 1)) {
+ long newBlockAddress = resizeBlock(nd, address, newGrowableSize);
+
+ GROWABLE_BLOCK_ADDRESS.put(nd, address, newBlockAddress);
+ }
+ }
+
+ /**
+ * Returns the number of elements that should actually be allocated in the growable region for an array of the given
+ * size
+ */
+ private int getGrowableRegionSizeFor(int arraySize) {
+ int growableRegionSize = arraySize - this.inlineSize;
+
+ if (growableRegionSize <= 0) {
+ return 0;
+ }
+
+ // Find the next power of two that is equal or greater than the required size. We use inlineSize
+ // as the minimum growable block size since we tend to assign a large inlineSize to lists with a large
+ // average number of elements, and these are also the lists that will benefit from a larger initial block size.
+ int nextGrowableSize = getNextPowerOfTwo(Math.max(growableRegionSize, this.inlineSize));
+
+ if (nextGrowableSize > GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+ // If the next power of two is greater than the max block size but the requested size is smaller than it,
+ // clamp it to the the max block size
+ if (growableRegionSize <= GrowableBlockHeader.MAX_GROWABLE_SIZE) {
+ return GrowableBlockHeader.MAX_GROWABLE_SIZE;
+ }
+
+ // For sizes larger than the max block size, we need to use a metablock. In this case, the allocated size
+ // will be a multiple of the max block size.
+ return roundUpToMultipleOf(GrowableBlockHeader.MAX_GROWABLE_SIZE, growableRegionSize);
+ }
+
+ return nextGrowableSize;
+ }
+
+ /**
+ * Returns the largest power of two that is less than or equal to the given integer
+ */
+ private static int getPrevPowerOfTwo(int n) {
+ n |= (n >> 1);
+ n |= (n >> 2);
+ n |= (n >> 4);
+ n |= (n >> 8);
+ n |= (n >> 16);
+ return n - (n >> 1);
+ }
+
+ /**
+ * Returns the next power of two that is equal to or greater than the given int.
+ */
+ private static int getNextPowerOfTwo(int toTest) {
+ int highBit = getPrevPowerOfTwo(toTest);
+ int nextGrowableSize = highBit;
+
+ if (highBit != toTest) {
+ assert (nextGrowableSize << 1) != 0;
+ nextGrowableSize <<= 1;
+ }
+ return nextGrowableSize;
+ }
+
+ /**
+ * Rounds a value up to the nearest multiple of another value
+ */
+ private static int roundUpToMultipleOf(int unit, int valueToRound) {
+ int numberOfMetablocks = (valueToRound + unit - 1) / unit;
+
+ return numberOfMetablocks * unit;
+ }
+
+ /**
+ * Returns the record size for a RawGrowableSize with the given number of inline records
+ */
+ public int getRecordSize() {
+ return ARRAY_HEADER_BYTES + Database.PTR_SIZE * this.inlineSize;
+ }
+
+ public void destruct(Nd nd, long address) {
+ repackIfNecessary(nd, address, 0);
+ }
+
+
+ /**
+ * Returns true iff the size of the array is 0
+ *
+ * @param address address of the array
+ * @return the array size, in number elements
+ */
+ public boolean isEmpty(Nd nd, long address) {
+ Database db = nd.getDB();
+ long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+ if (growableBlockAddress == 0) {
+ if (this.inlineSize == 0) {
+ return true;
+ }
+ // If there is no growable block or metablock, then the size is determined by the position of the first
+ // null pointer among the inline records.
+ long firstValue = db.getRecPtr(address + ARRAY_HEADER_BYTES);
+
+ return firstValue == 0;
+ }
+ return GrowableBlockHeader.ARRAY_SIZE.get(nd, growableBlockAddress) == 0;
+ }
+
+ public int getCapacity(Nd nd, long address) {
+ long growableBlockAddress = GROWABLE_BLOCK_ADDRESS.get(nd, address);
+
+ if (growableBlockAddress == 0) {
+ return this.inlineSize;
+ }
+
+ int growableBlockCurrentSize = GrowableBlockHeader.ALLOCATED_SIZE.get(nd, growableBlockAddress);
+
+ return growableBlockCurrentSize + this.inlineSize;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/StreamHasher.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/StreamHasher.java
new file mode 100644
index 000000000..ab1642d8d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/StreamHasher.java
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Sergey Prigogin (Google) - initial API and implementation
+ *
+ * Based on lookup3.c, by Bob Jenkins {@link "http://burtleburtle.net/bob/c/lookup3.c"}
+ *
+ * Here is the original comment by Bob Jenkins:
+ * -------------------------------------------------------------------------------
+ * lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+ *
+ * These are functions for producing 32-bit hashes for hash table lookup.
+ * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+ * are externally useful functions. Routines to test the hash are included
+ * if SELF_TEST is defined. You can use this free for any purpose. It's in
+ * the public domain. It has no warranty.
+ *
+ * You probably want to use hashlittle(). hashlittle() and hashbig()
+ * hash byte arrays. hashlittle() is is faster than hashbig() on
+ * little-endian machines. Intel and AMD are little-endian machines.
+ * On second thought, you probably want hashlittle2(), which is identical to
+ * hashlittle() except it returns two 32-bit hashes for the price of one.
+ * You could implement hashbig2() if you wanted but I haven't bothered here.
+ *
+ * If you want to find a hash of, say, exactly 7 integers, do
+ * a = i1; b = i2; c = i3;
+ * mix(a, b, c);
+ * a += i4; b += i5; c += i6;
+ * mix(a, b, c);
+ * a += i7;
+ * finalMix(a, b, c);
+ * then use c as the hash value. If you have a variable length array of
+ * 4-byte integers to hash, use hashword(). If you have a byte array (like
+ * a character string), use hashlittle(). If you have several byte arrays, or
+ * a mix of things, see the comments above hashlittle().
+ *
+ * Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
+ * then mix those integers. This is fast (you can do a lot more thorough
+ * mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+ * on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd;
+
+/**
+ * Computes a 64-bit hash value of a character stream that can be supplied one chunk at a time.
+ * Usage:
+ * <pre>
+ * StreamHasher hasher = new StreamHasher();
+ * for (long offset = 0; offset < streamLength; offset += chunkLength) {
+ * hasher.addChunk(offset, chunkOfCharacters);
+ * }
+ * int64 hashValue = hasher.computeHash();
+ * </pre>
+ *
+ * Based on lookup3.c by Bob Jenkins from {@link "http://burtleburtle.net/bob/c/lookup3.c"}
+ */
+public final class StreamHasher {
+ private static final long SEED = 3141592653589793238L; // PI
+ private static final long EMPTY_STRING_HASH = new StreamHasher().computeHashInternal();
+
+ long hashedOffset; // Current position in the stream of characters.
+ int state; // Current position in the stream of characters modulo 6, or -1 after computeHash is called.
+ int a;
+ int b;
+ int c;
+ char previousCharacter;
+
+ public StreamHasher() {
+ // Set up the internal state.
+ this.hashedOffset = 0;
+ this.state = 0;
+ this.a = this.b = this.c = (int) SEED;
+ this.c += SEED >>> 32;
+ }
+
+ /**
+ * Adds a chunk of data to the hasher.
+ * @param chunk Contents of the chunk.
+ */
+ public void addChunk(char[] chunk) {
+ for (int pos = 0; pos < chunk.length; pos++, this.hashedOffset++) {
+ char cc = chunk[pos];
+ switch (this.state++) {
+ case -1:
+ throw new IllegalStateException("addChunk is called after computeHash."); //$NON-NLS-1$
+ case 0:
+ case 2:
+ case 4:
+ this.previousCharacter = cc;
+ break;
+ case 1:
+ this.a += this.previousCharacter | (cc << 16);
+ break;
+ case 3:
+ this.b += this.previousCharacter | (cc << 16);
+ break;
+ case 5:
+ this.c += this.previousCharacter | (cc << 16);
+ mix();
+ this.state = 0;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Computes and returns the hash value. Must be called once after the last chunk.
+ * @return The hash value of the character stream.
+ */
+ public long computeHash() {
+ if (this.state < 0) {
+ throw new IllegalStateException("computeHash method is called more than once."); //$NON-NLS-1$
+ }
+ return computeHashInternal() ^ EMPTY_STRING_HASH;
+ }
+
+ private long computeHashInternal() {
+ switch (this.state) {
+ case 1:
+ this.a += this.previousCharacter;
+ break;
+ case 3:
+ this.b += this.previousCharacter;
+ break;
+ case 5:
+ this.c += this.previousCharacter;
+ break;
+ }
+ this.state = -1; // Protect against subsequent calls.
+ finalMix();
+ return (this.c & 0xFFFFFFFFL) | ((long) this.b << 32);
+ }
+
+ /**
+ * Computes a 64-bit hash value of a String. The resulting hash value
+ * is zero if the string is empty.
+ *
+ * @param str The string to hash.
+ * @return The hash value.
+ */
+ public static long hash(String str) {
+ StreamHasher hasher = new StreamHasher();
+ hasher.addChunk(str.toCharArray());
+ return hasher.computeHash();
+ }
+
+ /**
+ * Mixes three 32-bit values reversibly.
+ *
+ * This is reversible, so any information in a, b, c before mix() is
+ * still in a, b, c after mix().
+ *
+ * If four pairs of a, b, c inputs are run through mix(), or through
+ * mix() in reverse, there are at least 32 bits of the output that
+ * are sometimes the same for one pair and different for another pair.
+ * This was tested for:
+ * * pairs that differed by one bit, by two bits, in any combination
+ * of top bits of a, b, c, or in any combination of bottom bits of
+ * a, b, c.
+ * * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ * the output delta to a Gray code (a ^ (a >> 1)) so a string of 1's
+ * (as is commonly produced by subtraction) look like a single 1-bit
+ * difference.
+ * * the base values were pseudo-random, all zero but one bit set, or
+ * all zero plus a counter that starts at zero.
+ *
+ * Some k values for my "a -= c; a ^= Integer.rotateLeft(c, k); c += b;"
+ * arrangement that satisfy this are
+ * 4 6 8 16 19 4
+ * 9 15 3 18 27 15
+ * 14 9 3 7 17 3
+ * Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+ * for "differ" defined as + with a one-bit base and a two-bit delta.
+ * I used http://burtleburtle.net/bob/hash/avalanche.html to choose
+ * the operations, constants, and arrangements of the variables.
+ *
+ * This does not achieve avalanche. There are input bits of a, b, c
+ * that fail to affect some output bits of a, b, c, especially of a.
+ * The most thoroughly mixed value is c, but it doesn't really even
+ * achieve avalanche in c.
+ *
+ * This allows some parallelism. Read-after-writes are good at doubling
+ * the number of bits affected, so the goal of mixing pulls in the opposite
+ * direction as the goal of parallelism. I did what I could. Rotates
+ * seem to cost as much as shifts on every machine I could lay my hands
+ * on, and rotates are much kinder to the top and bottom bits, so I used
+ * rotates.
+ */
+ private void mix() {
+ this.a -= this.c; this.a ^= Integer.rotateLeft(this.c, 4); this.c += this.b;
+ this.b -= this.a; this.b ^= Integer.rotateLeft(this.a, 6); this.a += this.c;
+ this.c -= this.b; this.c ^= Integer.rotateLeft(this.b, 8); this.b += this.a;
+ this.a -= this.c; this.a ^= Integer.rotateLeft(this.c, 16); this.c += this.b;
+ this.b -= this.a; this.b ^= Integer.rotateLeft(this.a, 19); this.a += this.c;
+ this.c -= this.b; this.c ^= Integer.rotateLeft(this.b, 4); this.b += this.a;
+ }
+
+ /**
+ * Final mixing of 3 32-bit values a, b, c into c
+ *
+ * Pairs of a, b, c values differing in only a few bits will usually
+ * produce values of c that look totally different. This was tested for
+ * * pairs that differed by one bit, by two bits, in any combination
+ * of top bits of a, b, c, or in any combination of bottom bits of
+ * a, b, c.
+ * * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ * the output delta to a Gray code (a ^ (a >> 1)) so a string of 1's (as
+ * is commonly produced by subtraction) look like a single 1-bit
+ * difference.
+ * * the base values were pseudo-random, all zero but one bit set, or
+ * all zero plus a counter that starts at zero.
+ *
+ * These constants passed:
+ * 14 11 25 16 4 14 24
+ * 12 14 25 16 4 14 24
+ * and these came close:
+ * 4 8 15 26 3 22 24
+ * 10 8 15 26 3 22 24
+ * 11 8 15 26 3 22 24
+ */
+ private void finalMix() {
+ this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 14);
+ this.a ^= this.c; this.a -= Integer.rotateLeft(this.c, 11);
+ this.b ^= this.a; this.b -= Integer.rotateLeft(this.a, 25);
+ this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 16);
+ this.a ^= this.c; this.a -= Integer.rotateLeft(this.c, 4);
+ this.b ^= this.a; this.b -= Integer.rotateLeft(this.a, 14);
+ this.c ^= this.b; this.c -= Integer.rotateLeft(this.b, 24);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/BTree.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/BTree.java
new file mode 100644
index 000000000..0d047882a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/BTree.java
@@ -0,0 +1,773 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ * Andrew Ferguson (Symbian) - Provide B-tree deletion routine
+ * Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.text.MessageFormat;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.core.nd.AbstractTypeFactory;
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Implements B-Tree search structure.
+ */
+public class BTree {
+ private static final int DEFAULT_DEGREE = 8;
+ // Constants for internal deletion routine (see deleteImp doc).
+ private static final int DELMODE_NORMAL = 0;
+ private static final int DELMODE_DELETE_MINIMUM = 1;
+ private static final int DELMODE_DELETE_MAXIMUM = 2;
+
+ public static final int RECORD_SIZE = Database.PTR_SIZE;
+
+ private final Nd nd;
+ protected final Database db;
+ protected final long rootPointer;
+
+ protected final int degree;
+ protected final int maxRecords;
+ protected final int maxChildren;
+ protected final int minRecords;
+ protected final int offsetChildren;
+ protected final int medianRecord;
+
+ protected final IBTreeComparator cmp;
+
+ public BTree(Nd nd, long rootPointer, IBTreeComparator cmp) {
+ this(nd, rootPointer, DEFAULT_DEGREE, cmp);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param nd the database containing the btree
+ * @param rootPointer offset into database of the pointer to the root node
+ */
+ public BTree(Nd nd, long rootPointer, int degree, IBTreeComparator cmp) {
+ this.nd = nd;
+ if (degree < 2)
+ throw new IllegalArgumentException("Illegal degree " + degree + " in tree"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ this.db = nd.getDB();
+ this.rootPointer = rootPointer;
+ this.cmp = cmp;
+
+ this.degree = degree;
+ this.minRecords = this.degree - 1;
+ this.maxRecords = 2*this.degree - 1;
+ this.maxChildren = 2*this.degree;
+ this.offsetChildren = this.maxRecords * Database.INT_SIZE;
+ this.medianRecord = this.degree - 1;
+ }
+
+ public static ITypeFactory<BTree> getFactory(final IBTreeComparator cmp) {
+ return getFactory(8, cmp);
+ }
+
+ public static ITypeFactory<BTree> getFactory(final int degree, final IBTreeComparator cmp) {
+ return new AbstractTypeFactory<BTree>() {
+ @Override
+ public BTree create(Nd dom, long address) {
+ return new BTree(dom, address, degree, cmp);
+ }
+
+ @Override
+ public int getRecordSize() {
+ return RECORD_SIZE;
+ }
+
+ @Override
+ public Class<?> getElementClass() {
+ return BTree.class;
+ }
+
+ @Override
+ public void destruct(Nd dom, long address) {
+ destructFields(dom, address);
+ }
+
+ @Override
+ public void destructFields(Nd dom, long address) {
+ create(dom, address).destruct();
+ }
+ };
+ }
+
+ protected long getRoot() throws IndexException {
+ return this.db.getRecPtr(this.rootPointer);
+ }
+
+ protected final void putRecord(Chunk chunk, long node, int index, long record) {
+ chunk.putRecPtr(node + index * Database.INT_SIZE, record);
+ }
+
+ protected final long getRecord(Chunk chunk, long node, int index) {
+ return chunk.getRecPtr(node + index * Database.INT_SIZE);
+ }
+
+ protected final void putChild(Chunk chunk, long node, int index, long child) {
+ chunk.putRecPtr(node + this.offsetChildren + index * Database.INT_SIZE, child);
+ }
+
+ protected final long getChild(Chunk chunk, long node, int index) {
+ return chunk.getRecPtr(node + this.offsetChildren + index * Database.INT_SIZE);
+ }
+
+ public void destruct() {
+ long root = getRoot();
+
+ if (root == 0) {
+ return;
+ }
+
+ deallocateChildren(root);
+ }
+
+ private void deallocateChildren(long record) {
+ Chunk chunk = this.db.getChunk(record);
+
+ // Copy all the children pointers to an array of longs so all the reads will happen on the same chunk
+ // consecutively
+ long[] children = new long[this.maxRecords + 1];
+
+ for (int idx = 0; idx < children.length; idx++) {
+ children[idx] = getChild(chunk, record, idx);
+ }
+
+ this.db.free(record, Database.POOL_BTREE);
+
+ chunk = null;
+
+ for (long nextChild : children) {
+ if (nextChild != 0) {
+ deallocateChildren(nextChild);
+ }
+ }
+ }
+
+ /**
+ * Inserts the record into the b-tree. We don't insert if the key was already there,
+ * in which case we return the record that matched. In other cases, we just return
+ * the record back.
+ *
+ * @param record offset of the record
+ */
+ public long insert(long record) throws IndexException {
+ long root = getRoot();
+
+ // Is this our first time in.
+ if (root == 0) {
+ firstInsert(record);
+ return record;
+ }
+
+ return insert(null, 0, 0, root, record);
+ }
+
+ private long insert(Chunk pChunk, long parent, int iParent, long node, long record) throws IndexException {
+ Chunk chunk = this.db.getChunk(node);
+
+ // If this node is full (last record isn't null), split it.
+ if (getRecord(chunk, node, this.maxRecords - 1) != 0) {
+ long median = getRecord(chunk, node, this.medianRecord);
+ if (median == record) {
+ // Found it, never mind.
+ return median;
+ } else {
+ // Split it.
+ // Create the new node and move the larger records over.
+ long newnode = allocateNode();
+ Chunk newchunk = this.db.getChunk(newnode);
+ for (int i = 0; i < this.medianRecord; ++i) {
+ putRecord(newchunk, newnode, i, getRecord(chunk, node, this.medianRecord + 1 + i));
+ putRecord(chunk, node, this.medianRecord + 1 + i, 0);
+ putChild(newchunk, newnode, i, getChild(chunk, node, this.medianRecord + 1 + i));
+ putChild(chunk, node, this.medianRecord + 1 + i, 0);
+ }
+ putChild(newchunk, newnode, this.medianRecord, getChild(chunk, node, this.maxRecords));
+ putChild(chunk, node, this.maxRecords, 0);
+
+ if (parent == 0) {
+ // Create a new root
+ parent = allocateNode();
+ pChunk = this.db.getChunk(parent);
+ this.db.putRecPtr(this.rootPointer, parent);
+ putChild(pChunk, parent, 0, node);
+ } else {
+ // Insert the median into the parent.
+ for (int i = this.maxRecords - 2; i >= iParent; --i) {
+ long r = getRecord(pChunk, parent, i);
+ if (r != 0) {
+ putRecord(pChunk, parent, i + 1, r);
+ putChild(pChunk, parent, i + 2, getChild(pChunk, parent, i + 1));
+ }
+ }
+ }
+ putRecord(pChunk, parent, iParent, median);
+ putChild(pChunk, parent, iParent + 1, newnode);
+
+ putRecord(chunk, node, this.medianRecord, 0);
+
+ // Set the node to the correct one to follow.
+ if (this.cmp.compare(this.nd, record, median) > 0) {
+ node = newnode;
+ chunk = newchunk;
+ }
+ }
+ }
+
+ // Binary search to find the insert point.
+ int lower= 0;
+ int upper= this.maxRecords - 1;
+ while (lower < upper && getRecord(chunk, node, upper - 1) == 0) {
+ upper--;
+ }
+
+ while (lower < upper) {
+ int middle= (lower + upper) / 2;
+ long checkRec= getRecord(chunk, node, middle);
+ if (checkRec == 0) {
+ upper= middle;
+ } else {
+ int compare= this.cmp.compare(this.nd, checkRec, record);
+ if (compare > 0) {
+ upper= middle;
+ } else if (compare < 0) {
+ lower= middle + 1;
+ } else {
+ // Found it, no insert, just return the matched record.
+ return checkRec;
+ }
+ }
+ }
+ final int i= lower;
+ long child = getChild(chunk, node, i);
+ if (child != 0) {
+ // Visit the children.
+ return insert(chunk, node, i, child, record);
+ } else {
+ // We are at the leaf, add us in.
+ // First copy everything after over one.
+ for (int j = this.maxRecords - 2; j >= i; --j) {
+ long r = getRecord(chunk, node, j);
+ if (r != 0)
+ putRecord(chunk, node, j + 1, r);
+ }
+ putRecord(chunk, node, i, record);
+ return record;
+ }
+ }
+
+ private void firstInsert(long record) throws IndexException {
+ // Create the node and save it as root.
+ long root = allocateNode();
+ this.db.putRecPtr(this.rootPointer, root);
+ // Put the record in the first slot of the node.
+ putRecord(this.db.getChunk(root), root, 0, record);
+ }
+
+ private long allocateNode() throws IndexException {
+ return this.db.malloc((2 * this.maxRecords + 1) * Database.INT_SIZE, Database.POOL_BTREE);
+ }
+
+ /**
+ * Deletes the specified record from the B-tree.
+ * <p>
+ * If the specified record is not present then this routine has no effect.
+ * <p>
+ * Specifying a record r for which there is another record q existing in the B-tree
+ * where cmp.compare(r,q)==0 && r!=q will also have no effect
+ * <p>
+ * N.B. The record is not deleted itself - its storage is not deallocated.
+ * The reference to the record in the btree is deleted.
+ *
+ * @param record the record to delete
+ * @throws IndexException
+ */
+ public void delete(long record) throws IndexException {
+ try {
+ deleteImp(record, getRoot(), DELMODE_NORMAL);
+ } catch (BTreeKeyNotFoundException e) {
+ // Contract of this method is to NO-OP upon this event.
+ }
+ }
+
+ private class BTreeKeyNotFoundException extends Exception {
+ private static final long serialVersionUID = 9065438266175091670L;
+ public BTreeKeyNotFoundException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Used in implementation of delete routines
+ */
+ private class BTNode {
+ final long node;
+ final int keyCount;
+ final Chunk chunk;
+
+ BTNode(long node) throws IndexException {
+ this.node = node;
+ this.chunk = BTree.this.db.getChunk(node);
+ int i= 0;
+ while (i < BTree.this.maxRecords && getRecord(this.chunk, node, i) != 0)
+ i++;
+ this.keyCount = i;
+ }
+
+ BTNode getChild(int index) throws IndexException {
+ if (0 <= index && index < BTree.this.maxChildren) {
+ long child = BTree.this.getChild(this.chunk, this.node, index);
+ if (child != 0)
+ return new BTNode(child);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Implementation for deleting a key/record from the B-tree.
+ * <p>
+ * There is no distinction between keys and records.
+ * <p>
+ * This implements a single downward pass (with minor exceptions) deletion
+ * <p>
+ * @param key the address of the record to delete
+ * @param nodeRecord a node that (directly or indirectly) contains the specified key/record
+ * @param mode one of DELMODE_NORMAL, DELMODE_DELETE_MINIMUM, DELMODE_DELETE_MAXIMUM
+ * where DELMODE_NORMAL: locates the specified key/record using the comparator provided
+ * DELMODE_DELETE_MINIMUM: locates and deletes the minimum element in the subtree rooted at nodeRecord
+ * DELMODE_DELETE_MAXIMUM: locates and deletes the maximum element in the subtree rooted at nodeRecord
+ * @return the address of the record removed from the B-tree
+ * @throws IndexException
+ */
+ private long deleteImp(long key, long nodeRecord, int mode)
+ throws IndexException, BTreeKeyNotFoundException {
+ BTNode node = new BTNode(nodeRecord);
+
+ // Determine index of key in current node, or -1 if its not in this node.
+ int keyIndexInNode = -1;
+ if (mode == DELMODE_NORMAL)
+ for (int i= 0; i < node.keyCount; i++)
+ if (getRecord(node.chunk, node.node, i) == key) {
+ keyIndexInNode = i;
+ break;
+ }
+
+ if (getChild(node.chunk, node.node, 0) == 0) {
+ /* Case 1: leaf node containing the key (by method precondition) */
+ if (keyIndexInNode != -1) {
+ nodeContentDelete(node, keyIndexInNode, 1);
+ return key;
+ } else {
+ if (mode == DELMODE_DELETE_MINIMUM) {
+ long subst = getRecord(node.chunk, node.node, 0);
+ nodeContentDelete(node, 0, 1);
+ return subst;
+ } else if (mode == DELMODE_DELETE_MAXIMUM) {
+ long subst = getRecord(node.chunk, node.node, node.keyCount - 1);
+ nodeContentDelete(node, node.keyCount - 1, 1);
+ return subst;
+ }
+ throw new BTreeKeyNotFoundException("Deletion on absent key " + key + ", mode = " + mode); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ } else {
+ if (keyIndexInNode != -1) {
+ /* Case 2: non-leaf node which contains the key itself */
+
+ BTNode succ = node.getChild(keyIndexInNode + 1);
+ if (succ != null && succ.keyCount > this.minRecords) {
+ /* Case 2a: Delete key by overwriting it with its successor (which occurs in a leaf node) */
+ long subst = deleteImp(-1, succ.node, DELMODE_DELETE_MINIMUM);
+ putRecord(node.chunk, node.node, keyIndexInNode, subst);
+ return key;
+ }
+
+ BTNode pred = node.getChild(keyIndexInNode);
+ if (pred != null && pred.keyCount > this.minRecords) {
+ /* Case 2b: Delete key by overwriting it with its predecessor (which occurs in a leaf node) */
+ long subst = deleteImp(-1, pred.node, DELMODE_DELETE_MAXIMUM);
+ putRecord(node.chunk, node.node, keyIndexInNode, subst);
+ return key;
+ }
+
+ /* Case 2c: Merge successor and predecessor */
+ // assert(pred != null && succ != null);
+ if (pred != null) {
+ mergeNodes(succ, node, keyIndexInNode, pred);
+ return deleteImp(key, pred.node, mode);
+ }
+ return key;
+ } else {
+ /* Case 3: non-leaf node which does not itself contain the key */
+
+ /* Determine root of subtree that should contain the key */
+ int subtreeIndex;
+ switch(mode) {
+ case DELMODE_NORMAL:
+ subtreeIndex = node.keyCount;
+ for (int i= 0; i < node.keyCount; i++)
+ if (this.cmp.compare(this.nd, getRecord(node.chunk, node.node, i), key)>0) {
+ subtreeIndex = i;
+ break;
+ }
+ break;
+ case DELMODE_DELETE_MINIMUM: subtreeIndex = 0; break;
+ case DELMODE_DELETE_MAXIMUM: subtreeIndex = node.keyCount; break;
+ default: throw new IndexException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, IStatus.OK, "Unknown delete mode " + mode, null)); //$NON-NLS-1$
+ }
+
+ BTNode child = node.getChild(subtreeIndex);
+ if (child == null) {
+ throw new IndexException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, IStatus.OK,
+ "BTree integrity error (null child found)", null)); //$NON-NLS-1$
+ }
+
+ if (child.keyCount > this.minRecords) {
+ return deleteImp(key, child.node, mode);
+ } else {
+ BTNode sibR = node.getChild(subtreeIndex + 1);
+ if (sibR != null && sibR.keyCount > this.minRecords) {
+ /* Case 3a (i): child will underflow upon deletion, take a key from rightSibling */
+ long rightKey = getRecord(node.chunk, node.node, subtreeIndex);
+ long leftmostRightSiblingKey = getRecord(sibR.chunk, sibR.node, 0);
+ append(child, rightKey, getChild(sibR.chunk, sibR.node, 0));
+ nodeContentDelete(sibR, 0, 1);
+ putRecord(node.chunk, node.node, subtreeIndex, leftmostRightSiblingKey);
+ return deleteImp(key, child.node, mode);
+ }
+
+ BTNode sibL = node.getChild(subtreeIndex - 1);
+ if (sibL != null && sibL.keyCount > this.minRecords) {
+ /* Case 3a (ii): child will underflow upon deletion, take a key from leftSibling */
+ long leftKey = getRecord(node.chunk, node.node, subtreeIndex - 1);
+ prepend(child, leftKey, getChild(sibL.chunk, sibL.node, sibL.keyCount));
+ long rightmostLeftSiblingKey = getRecord(sibL.chunk, sibL.node, sibL.keyCount - 1);
+ putRecord(sibL.chunk, sibL.node, sibL.keyCount - 1, 0);
+ putChild(sibL.chunk, sibL.node, sibL.keyCount, 0);
+ putRecord(node.chunk, node.node, subtreeIndex - 1, rightmostLeftSiblingKey);
+ return deleteImp(key, child.node, mode);
+ }
+
+ /* Case 3b (i,ii): leftSibling, child, rightSibling all have minimum number of keys */
+
+ if (sibL != null) { // merge child into leftSibling
+ mergeNodes(child, node, subtreeIndex - 1, sibL);
+ return deleteImp(key, sibL.node, mode);
+ }
+
+ if (sibR != null) { // merge rightSibling into child
+ mergeNodes(sibR, node, subtreeIndex, child);
+ return deleteImp(key, child.node, mode);
+ }
+
+ throw new BTreeKeyNotFoundException(
+ MessageFormat.format("Deletion of key not in btree: {0} mode={1}", //$NON-NLS-1$
+ new Object[]{new Long(key), new Integer(mode)}));
+ }
+ }
+ }
+ }
+
+ /**
+ * Merge node 'src' onto the right side of node 'dst' using node
+ * 'keyProvider' as the source of the median key. Bounds checking is not
+ * performed.
+ * @param src the key to merge into dst
+ * @param keyProvider the node that provides the median key for the new node
+ * @param kIndex the index of the key in the node <i>mid</i> which is to become the new node's median key
+ * @param dst the node which is the basis and result of the merge
+ */
+ public void mergeNodes(BTNode src, BTNode keyProvider, int kIndex, BTNode dst)
+ throws IndexException {
+ nodeContentCopy(src, 0, dst, dst.keyCount + 1, src.keyCount + 1);
+ long midKey = getRecord(keyProvider.chunk, keyProvider.node, kIndex);
+ putRecord(dst.chunk, dst.node, dst.keyCount, midKey);
+ long keySucc = kIndex + 1 == this.maxRecords ? 0 : getRecord(keyProvider.chunk, keyProvider.node, kIndex + 1);
+ this.db.free(getChild(keyProvider.chunk, keyProvider.node, kIndex + 1), Database.POOL_BTREE);
+ nodeContentDelete(keyProvider, kIndex + 1, 1);
+ putRecord(keyProvider.chunk, keyProvider.node, kIndex, keySucc);
+ if (kIndex == 0 && keySucc == 0) {
+ /*
+ * The root node is excused from the property that a node must have a least MIN keys
+ * This means we must special case it at the point when its had all of its keys deleted
+ * entirely during merge operations (which push one of its keys down as a pivot)
+ */
+ long rootNode = getRoot();
+ if (rootNode == keyProvider.node) {
+ this.db.putRecPtr(this.rootPointer, dst.node);
+ this.db.free(rootNode, Database.POOL_BTREE);
+ }
+ }
+ }
+
+ /**
+ * Insert the key and (its predecessor) child at the left side of the specified node. Bounds checking
+ * is not performed.
+ * @param node the node to prepend to
+ * @param key the new leftmost (least) key
+ * @param child the new leftmost (least) subtree root
+ */
+ private void prepend(BTNode node, long key, long child) {
+ nodeContentCopy(node, 0, node, 1, node.keyCount + 1);
+ putRecord(node.chunk, node.node, 0, key);
+ putChild(node.chunk, node.node, 0, child);
+ }
+
+ /**
+ * Insert the key and (its successor) child at the right side of the specified node. Bounds
+ * checking is not performed.
+ * @param node
+ * @param key
+ * @param child
+ */
+ private void append(BTNode node, long key, long child) {
+ putRecord(node.chunk, node.node, node.keyCount, key);
+ putChild(node.chunk, node.node, node.keyCount + 1, child);
+ }
+
+ /**
+ * Overwrite a section of the specified node (dst) with the specified section of the source
+ * node. Bounds checking is not performed. To allow just copying of the final child (which has
+ * no corresponding key) the routine behaves as though there were a corresponding key existing
+ * with value zero.<p>
+ * Copying from a node to itself is permitted.
+ * @param src the node to read from
+ * @param srcPos the initial index to read from (inclusive)
+ * @param dst the node to write to
+ * @param dstPos the initial index to write to (inclusive)
+ * @param length the number of (key,(predecessor)child) nodes to write
+ */
+ private void nodeContentCopy(BTNode src, int srcPos, BTNode dst, int dstPos, int length) {
+ for (int i=length - 1; i >= 0; i--) { // this order is important when src == dst!
+ int srcIndex = srcPos + i;
+ int dstIndex = dstPos + i;
+
+ if (srcIndex < src.keyCount + 1) {
+ long srcChild = getChild(src.chunk, src.node, srcIndex);
+ putChild(dst.chunk, dst.node, dstIndex, srcChild);
+
+ if (srcIndex < src.keyCount) {
+ long srcKey = getRecord(src.chunk, src.node, srcIndex);
+ putRecord(dst.chunk, dst.node, dstIndex, srcKey);
+ }
+ }
+ }
+ }
+
+ /**
+ * Delete a section of node content - (key, (predecessor)child) pairs. Bounds checking
+ * is not performed. To allow deletion of the final child (which has no corresponding key)
+ * the routine behaves as though there were a corresponding key existing with value zero.<p>
+ * Content is deleted and remaining content is moved leftward the appropriate amount.
+ * @param node the node to delete content from
+ * @param i the start index (inclusive) to delete from
+ * @param length the length of the sequence to delete
+ */
+ private void nodeContentDelete(BTNode node, int i, int length) {
+ for (int index= i; index <= this.maxRecords; index++) {
+ long newKey = (index + length) < node.keyCount ? getRecord(node.chunk, node.node, index + length) : 0;
+ long newChild = (index + length) < node.keyCount + 1 ? getChild(node.chunk, node.node, index + length) : 0;
+ if (index < this.maxRecords) {
+ putRecord(node.chunk, node.node, index, newKey);
+ }
+ if (index < this.maxChildren) {
+ putChild(node.chunk, node.node, index, newChild);
+ }
+ }
+ }
+
+ /**
+ * Visit all nodes beginning when the visitor comparator
+ * returns >= 0 until the visitor visit returns falls.
+ *
+ * @param visitor
+ */
+ public boolean accept(IBTreeVisitor visitor) throws IndexException {
+ return accept(this.db.getRecPtr(this.rootPointer), visitor);
+ }
+
+ private boolean accept(long node, IBTreeVisitor visitor) throws IndexException {
+ // If found is false, we are still in search mode.
+ // Once found is true visit everything.
+ // Return false when ready to quit.
+
+ if (node == 0) {
+ return true;
+ }
+ if (visitor instanceof IBTreeVisitor2) {
+ ((IBTreeVisitor2) visitor).preNode(node);
+ }
+
+ try {
+ Chunk chunk = this.db.getChunk(node);
+
+ // Binary search to find first record greater or equal.
+ int lower= 0;
+ int upper= this.maxRecords - 1;
+ while (lower < upper && getRecord(chunk, node, upper - 1) == 0) {
+ upper--;
+ }
+ while (lower < upper) {
+ int middle= (lower + upper) / 2;
+ long checkRec = getRecord(chunk, node, middle);
+ if (checkRec == 0) {
+ upper= middle;
+ } else {
+ int compare= visitor.compare(checkRec);
+ if (compare >= 0) {
+ upper= middle;
+ } else {
+ lower= middle + 1;
+ }
+ }
+ }
+
+ // Start with first record greater or equal, reuse comparison results.
+ int i= lower;
+ for (; i < this.maxRecords; ++i) {
+ long record = getRecord(chunk, node, i);
+ if (record == 0)
+ break;
+
+ int compare= visitor.compare(record);
+ if (compare > 0) {
+ // Start point is to the left.
+ return accept(getChild(chunk, node, i), visitor);
+ } else if (compare == 0) {
+ if (!accept(getChild(chunk, node, i), visitor))
+ return false;
+ if (!visitor.visit(record))
+ return false;
+ }
+ }
+ return accept(getChild(chunk, node, i), visitor);
+ } finally {
+ if (visitor instanceof IBTreeVisitor2) {
+ ((IBTreeVisitor2) visitor).postNode(node);
+ }
+ }
+ }
+
+ /*
+ * TODO: It would be good to move these into IBTreeVisitor and eliminate
+ * IBTreeVisitor2 if this is acceptable.
+ */
+ private interface IBTreeVisitor2 extends IBTreeVisitor {
+ void preNode(long node) throws IndexException;
+ void postNode(long node) throws IndexException;
+ }
+
+ /**
+ * Debugging method for checking B-tree invariants
+ * @return the empty String if B-tree invariants hold, otherwise
+ * a human readable report
+ * @throws IndexException
+ */
+ public String getInvariantsErrorReport() throws IndexException {
+ InvariantsChecker checker = new InvariantsChecker();
+ accept(checker);
+ return checker.isValid() ? "" : checker.getMsg(); //$NON-NLS-1$
+ }
+
+ /**
+ * A B-tree visitor for checking some B-tree invariants.
+ * Note ordering invariants are not checked here.
+ */
+ private class InvariantsChecker implements IBTreeVisitor2 {
+ boolean valid = true;
+ String msg = ""; //$NON-NLS-1$
+ Integer leafDepth;
+ int depth;
+
+ public InvariantsChecker() {}
+ public String getMsg() { return this.msg; }
+ public boolean isValid() { return this.valid; }
+ @Override
+ public void postNode(long node) throws IndexException { this.depth--; }
+ @Override
+ public int compare(long record) throws IndexException { return 0; }
+ @Override
+ public boolean visit(long record) throws IndexException { return true; }
+
+ @Override
+ public void preNode(long node) throws IndexException {
+ this.depth++;
+
+ // Collect information for checking.
+ int keyCount = 0;
+ int indexFirstBlankKey = BTree.this.maxRecords;
+ int indexLastNonBlankKey = 0;
+ for (int i= 0; i < BTree.this.maxRecords; i++) {
+ if (getRecord(BTree.this.db.getChunk(node), node, i) != 0) {
+ keyCount++;
+ indexLastNonBlankKey = i;
+ } else if (indexFirstBlankKey == BTree.this.maxRecords) {
+ indexFirstBlankKey = i;
+ }
+ }
+
+ int childCount = 0;
+ for (int i= 0; i < BTree.this.maxChildren; i++) {
+ if (getChild(BTree.this.db.getChunk(node), node, i) != 0) {
+ childCount++;
+ }
+ }
+
+ // Check that non-blank keys are contiguous and blank key terminated.
+ if (indexFirstBlankKey != indexLastNonBlankKey + 1) {
+ boolean full = indexFirstBlankKey == BTree.this.maxRecords && indexLastNonBlankKey == BTree.this.maxRecords - 1;
+ boolean empty = indexFirstBlankKey == 0 && indexLastNonBlankKey == 0;
+ if (!full && !empty) {
+ this.valid = false;
+ this.msg += MessageFormat.format("[{0} blanks inconsistent b={1} nb={2}]", //$NON-NLS-1$
+ new Object[] { new Long(node), new Integer(indexFirstBlankKey),
+ new Integer(indexLastNonBlankKey) });
+ }
+ }
+
+ // Check: Key number constrains child numbers
+ if (childCount != 0 && childCount != keyCount + 1) {
+ this.valid = false;
+ this.msg += MessageFormat.format("[{0} wrong number of children with respect to key count]", //$NON-NLS-1$
+ new Object[] { new Long(node) });
+ }
+
+ // The root node is excused from the remaining node constraints.
+ if (node == BTree.this.db.getRecPtr(BTree.this.rootPointer)) {
+ return;
+ }
+
+ // Check: Non-root nodes must have a keyCount within a certain range
+ if (keyCount < BTree.this.minRecords || keyCount > BTree.this.maxRecords) {
+ this.valid = false;
+ this.msg += MessageFormat.format("[{0} key count out of range]", new Object[] { new Long(node) }); //$NON-NLS-1$
+ }
+
+ // Check: All leaf nodes are at the same depth
+ if (childCount == 0) {
+ if (this.leafDepth == null) {
+ this.leafDepth = new Integer(this.depth);
+ }
+ if (this.depth != this.leafDepth.intValue()) {
+ this.valid = false;
+ this.msg += "Leaf nodes at differing depths"; //$NON-NLS-1$
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Chunk.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Chunk.java
new file mode 100644
index 000000000..42194839c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Chunk.java
@@ -0,0 +1,324 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ * Markus Schorn (Wind River Systems)
+ * IBM Corporation
+ * Sergey Prigogin (Google)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Caches the content of a piece of the database.
+ */
+final class Chunk {
+ final private byte[] fBuffer= new byte[Database.CHUNK_SIZE];
+
+ final Database fDatabase;
+ final int fSequenceNumber;
+
+ boolean fCacheHitFlag;
+ boolean fDirty;
+ boolean fLocked; // locked chunks must not be released from cache.
+ int fCacheIndex= -1;
+
+ Chunk(Database db, int sequenceNumber) {
+ this.fDatabase= db;
+ this.fSequenceNumber= sequenceNumber;
+ }
+
+ void read() throws IndexException {
+ try {
+ final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
+ this.fDatabase.read(buf, (long) this.fSequenceNumber * Database.CHUNK_SIZE);
+ } catch (IOException e) {
+ throw new IndexException(new DBStatus(e));
+ }
+ }
+
+ /**
+ * Uninterruptable. Returns true iff an attempt was made to interrupt the flush with
+ * {@link Thread#interrupt()}.
+ */
+ boolean flush() throws IndexException {
+ boolean wasCanceled = false;
+ try {
+ final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
+ wasCanceled = this.fDatabase.write(buf, (long) this.fSequenceNumber * Database.CHUNK_SIZE);
+ } catch (IOException e) {
+ throw new IndexException(new DBStatus(e));
+ }
+ this.fDirty= false;
+ return wasCanceled;
+ }
+
+ private static int recPtrToIndex(final long offset) {
+ return (int) (offset & Database.OFFSET_IN_CHUNK_MASK);
+ }
+
+ public void putByte(final long offset, final byte value) {
+ assert this.fLocked;
+ this.fDirty= true;
+ this.fBuffer[recPtrToIndex(offset)]= value;
+ }
+
+ public byte getByte(final long offset) {
+ return this.fBuffer[recPtrToIndex(offset)];
+ }
+
+ public byte[] getBytes(final long offset, final int length) {
+ final byte[] bytes = new byte[length];
+ System.arraycopy(this.fBuffer, recPtrToIndex(offset), bytes, 0, length);
+ return bytes;
+ }
+
+ public void putBytes(final long offset, final byte[] bytes) {
+ assert this.fLocked;
+ this.fDirty= true;
+ System.arraycopy(bytes, 0, this.fBuffer, recPtrToIndex(offset), bytes.length);
+ }
+
+ public void putInt(final long offset, final int value) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx= recPtrToIndex(offset);
+ putInt(value, this.fBuffer, idx);
+ }
+
+ static final void putInt(final int value, final byte[] buffer, int idx) {
+ buffer[idx]= (byte) (value >> 24);
+ buffer[++idx]= (byte) (value >> 16);
+ buffer[++idx]= (byte) (value >> 8);
+ buffer[++idx]= (byte) (value);
+ }
+
+ public int getInt(final long offset) {
+ return getInt(this.fBuffer, recPtrToIndex(offset));
+ }
+
+ static final int getInt(final byte[] buffer, int idx) {
+ return ((buffer[idx] & 0xff) << 24) |
+ ((buffer[++idx] & 0xff) << 16) |
+ ((buffer[++idx] & 0xff) << 8) |
+ ((buffer[++idx] & 0xff) << 0);
+ }
+
+ /**
+ * A free Record Pointer is a pointer to a raw block, i.e. the
+ * pointer is not moved past the BLOCK_HEADER_SIZE.
+ */
+ static int compressFreeRecPtr(final long value) {
+ // This assert verifies the alignment. We expect the low bits to be clear.
+ assert (value & (Database.BLOCK_SIZE_DELTA - 1)) == 0;
+ final int dense = (int) (value >> Database.BLOCK_SIZE_DELTA_BITS);
+ return dense;
+ }
+
+ /**
+ * A free Record Pointer is a pointer to a raw block,
+ * i.e. the pointer is not moved past the BLOCK_HEADER_SIZE.
+ */
+ static long expandToFreeRecPtr(int value) {
+ /*
+ * We need to properly manage the integer that was read. The value will be sign-extended
+ * so if the most significant bit is set, the resulting long will look negative. By
+ * masking it with ((long)1 << 32) - 1 we remove all the sign-extended bits and just
+ * have an unsigned 32-bit value as a long. This gives us one more useful bit in the
+ * stored record pointers.
+ */
+ long address = value & 0xFFFFFFFFL;
+ return address << Database.BLOCK_SIZE_DELTA_BITS;
+ }
+
+ /**
+ * A Record Pointer is a pointer as returned by Database.malloc().
+ * This is a pointer to a block + BLOCK_HEADER_SIZE.
+ */
+ public void putRecPtr(final long offset, final long value) {
+ assert this.fLocked;
+ this.fDirty = true;
+ int idx = recPtrToIndex(offset);
+ Database.putRecPtr(value, this.fBuffer, idx);
+ }
+
+ /**
+ * A free Record Pointer is a pointer to a raw block,
+ * i.e. the pointer is not moved past the BLOCK_HEADER_SIZE.
+ */
+ public void putFreeRecPtr(final long offset, final long value) {
+ assert this.fLocked;
+ this.fDirty = true;
+ int idx = recPtrToIndex(offset);
+ putInt(compressFreeRecPtr(value), this.fBuffer, idx);
+ }
+
+ public long getRecPtr(final long offset) {
+ final int idx = recPtrToIndex(offset);
+ return Database.getRecPtr(this.fBuffer, idx);
+ }
+
+ public long getFreeRecPtr(final long offset) {
+ final int idx = recPtrToIndex(offset);
+ int value = getInt(this.fBuffer, idx);
+ return expandToFreeRecPtr(value);
+ }
+
+ public void put3ByteUnsignedInt(final long offset, final int value) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx= recPtrToIndex(offset);
+ this.fBuffer[idx]= (byte) (value >> 16);
+ this.fBuffer[++idx]= (byte) (value >> 8);
+ this.fBuffer[++idx]= (byte) (value);
+ }
+
+ public int get3ByteUnsignedInt(final long offset) {
+ int idx= recPtrToIndex(offset);
+ return ((this.fBuffer[idx] & 0xff) << 16) |
+ ((this.fBuffer[++idx] & 0xff) << 8) |
+ ((this.fBuffer[++idx] & 0xff) << 0);
+ }
+
+ public void putShort(final long offset, final short value) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx= recPtrToIndex(offset);
+ this.fBuffer[idx]= (byte) (value >> 8);
+ this.fBuffer[++idx]= (byte) (value);
+ }
+
+ public short getShort(final long offset) {
+ int idx= recPtrToIndex(offset);
+ return (short) (((this.fBuffer[idx] << 8) | (this.fBuffer[++idx] & 0xff)));
+ }
+
+ public long getLong(final long offset) {
+ int idx= recPtrToIndex(offset);
+ return ((((long) this.fBuffer[idx] & 0xff) << 56) |
+ (((long) this.fBuffer[++idx] & 0xff) << 48) |
+ (((long) this.fBuffer[++idx] & 0xff) << 40) |
+ (((long) this.fBuffer[++idx] & 0xff) << 32) |
+ (((long) this.fBuffer[++idx] & 0xff) << 24) |
+ (((long) this.fBuffer[++idx] & 0xff) << 16) |
+ (((long) this.fBuffer[++idx] & 0xff) << 8) |
+ (((long) this.fBuffer[++idx] & 0xff) << 0));
+ }
+
+ public double getDouble(long offset) {
+ return Double.longBitsToDouble(getLong(offset));
+ }
+
+ public float getFloat(long offset) {
+ return Float.intBitsToFloat(getInt(offset));
+ }
+
+ public void putLong(final long offset, final long value) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx= recPtrToIndex(offset);
+
+ this.fBuffer[idx]= (byte) (value >> 56);
+ this.fBuffer[++idx]= (byte) (value >> 48);
+ this.fBuffer[++idx]= (byte) (value >> 40);
+ this.fBuffer[++idx]= (byte) (value >> 32);
+ this.fBuffer[++idx]= (byte) (value >> 24);
+ this.fBuffer[++idx]= (byte) (value >> 16);
+ this.fBuffer[++idx]= (byte) (value >> 8);
+ this.fBuffer[++idx]= (byte) (value);
+ }
+
+ public void putChar(final long offset, final char value) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx= recPtrToIndex(offset);
+ this.fBuffer[idx]= (byte) (value >> 8);
+ this.fBuffer[++idx]= (byte) (value);
+ }
+
+ public void putChars(final long offset, char[] chars, int start, int len) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx= recPtrToIndex(offset)-1;
+ final int end= start + len;
+ for (int i = start; i < end; i++) {
+ char value= chars[i];
+ this.fBuffer[++idx]= (byte) (value >> 8);
+ this.fBuffer[++idx]= (byte) (value);
+ }
+ }
+
+ public void putCharsAsBytes(final long offset, char[] chars, int start, int len) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx= recPtrToIndex(offset)-1;
+ final int end= start + len;
+ for (int i = start; i < end; i++) {
+ char value= chars[i];
+ this.fBuffer[++idx]= (byte) (value);
+ }
+ }
+
+ public void putDouble(final long offset, double value) {
+ putLong(offset, Double.doubleToLongBits(value));
+ }
+
+ public void putFloat(final long offset, float value) {
+ putInt(offset, Float.floatToIntBits(value));
+ }
+
+ public char getChar(final long offset) {
+ int idx= recPtrToIndex(offset);
+ return (char) (((this.fBuffer[idx] << 8) | (this.fBuffer[++idx] & 0xff)));
+ }
+
+ public void getChars(final long offset, final char[] result, int start, int len) {
+ final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
+ buf.position(recPtrToIndex(offset));
+ buf.asCharBuffer().get(result, start, len);
+ }
+
+ public void getCharsFromBytes(final long offset, final char[] result, int start, int len) {
+ final int pos = recPtrToIndex(offset);
+ for (int i = 0; i < len; i++) {
+ result[start + i] = (char) (this.fBuffer[pos + i] & 0xff);
+ }
+ }
+
+ void clear(final long offset, final int length) {
+ assert this.fLocked;
+ this.fDirty= true;
+ int idx = recPtrToIndex(offset);
+ final int end = idx + length;
+ for (; idx < end; idx++) {
+ this.fBuffer[idx] = 0;
+ }
+ }
+
+ void put(final long offset, final byte[] data, final int len) {
+ put(offset, data, 0, len);
+ }
+
+ void put(final long offset, final byte[] data, int dataPos, final int len) {
+ assert this.fLocked;
+ this.fDirty = true;
+ int idx = recPtrToIndex(offset);
+ System.arraycopy(data, dataPos, this.fBuffer, idx, len);
+ }
+
+ public void get(final long offset, byte[] data) {
+ get(offset, data, 0, data.length);
+ }
+
+ public void get(final long offset, byte[] data, int dataPos, int len) {
+ int idx = recPtrToIndex(offset);
+ System.arraycopy(this.fBuffer, idx, data, dataPos, len);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ChunkCache.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ChunkCache.java
new file mode 100644
index 000000000..1cd3736d8
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ChunkCache.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2016 Wind River Systems, Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Markus Schorn - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+public final class ChunkCache {
+ private static ChunkCache sSharedInstance= new ChunkCache();
+
+ private Chunk[] fPageTable;
+ private boolean fTableIsFull;
+ private int fPointer;
+
+ public static ChunkCache getSharedInstance() {
+ return sSharedInstance;
+ }
+
+ public ChunkCache() {
+ this(5 * 1024 * 1024);
+ }
+
+ public ChunkCache(long maxSize) {
+ this.fPageTable= new Chunk[computeLength(maxSize)];
+ }
+
+ public synchronized void add(Chunk chunk, boolean locked) {
+ if (locked) {
+ chunk.fLocked= true;
+ }
+ if (chunk.fCacheIndex >= 0) {
+ chunk.fCacheHitFlag= true;
+ return;
+ }
+ if (this.fTableIsFull) {
+ evictChunk();
+ chunk.fCacheIndex= this.fPointer;
+ this.fPageTable[this.fPointer]= chunk;
+ } else {
+ chunk.fCacheIndex= this.fPointer;
+ this.fPageTable[this.fPointer]= chunk;
+
+ this.fPointer++;
+ if (this.fPointer == this.fPageTable.length) {
+ this.fPointer= 0;
+ this.fTableIsFull= true;
+ }
+ }
+ }
+
+ /**
+ * Evicts a chunk from the page table and the chunk table.
+ * After this method returns, {@link #fPointer} will contain
+ * the index of the evicted chunk within the page table.
+ */
+ private void evictChunk() {
+ /*
+ * Use the CLOCK algorithm to determine which chunk to evict.
+ * i.e., if the chunk in the current slot of the page table has been
+ * recently referenced (i.e. the reference flag is set), unset the
+ * reference flag and move to the next slot. Otherwise, evict the
+ * chunk in the current slot.
+ */
+ while (true) {
+ Chunk chunk = this.fPageTable[this.fPointer];
+ if (chunk.fCacheHitFlag) {
+ chunk.fCacheHitFlag= false;
+ this.fPointer= (this.fPointer + 1) % this.fPageTable.length;
+ } else {
+ chunk.fDatabase.releaseChunk(chunk);
+ chunk.fCacheIndex= -1;
+ this.fPageTable[this.fPointer] = null;
+ return;
+ }
+ }
+ }
+
+ public synchronized void remove(Chunk chunk) {
+ final int idx= chunk.fCacheIndex;
+ if (idx >= 0) {
+ if (this.fTableIsFull) {
+ this.fPointer= this.fPageTable.length-1;
+ this.fTableIsFull= false;
+ } else {
+ this.fPointer--;
+ }
+ chunk.fCacheIndex= -1;
+ final Chunk move= this.fPageTable[this.fPointer];
+ this.fPageTable[idx]= move;
+ move.fCacheIndex= idx;
+ this.fPageTable[this.fPointer]= null;
+ }
+ }
+
+ /**
+ * Returns the maximum size of the chunk cache in bytes.
+ */
+ public synchronized long getMaxSize() {
+ return (long) this.fPageTable.length * Database.CHUNK_SIZE;
+ }
+
+ /**
+ * Clears the page table and changes it to hold chunks with
+ * maximum total memory of <code>maxSize</code>.
+ * @param maxSize the total size of the chunks in bytes.
+ */
+ public synchronized void setMaxSize(long maxSize) {
+ final int newLength= computeLength(maxSize);
+ final int oldLength= this.fTableIsFull ? this.fPageTable.length : this.fPointer;
+ if (newLength > oldLength) {
+ Chunk[] newTable= new Chunk[newLength];
+ System.arraycopy(this.fPageTable, 0, newTable, 0, oldLength);
+ this.fTableIsFull= false;
+ this.fPointer= oldLength;
+ this.fPageTable= newTable;
+ } else {
+ for (int i= newLength; i < oldLength; i++) {
+ final Chunk chunk= this.fPageTable[i];
+ chunk.fDatabase.releaseChunk(chunk);
+ chunk.fCacheIndex= -1;
+ }
+ Chunk[] newTable= new Chunk[newLength];
+ System.arraycopy(this.fPageTable, 0, newTable, 0, newLength);
+ this.fTableIsFull= true;
+ this.fPointer= 0;
+ this.fPageTable= newTable;
+ }
+ }
+
+ private int computeLength(long maxSize) {
+ long maxLength= Math.min(maxSize / Database.CHUNK_SIZE, Integer.MAX_VALUE);
+ return Math.max(1, (int) maxLength);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBProperties.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBProperties.java
new file mode 100644
index 000000000..cfa6050bb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBProperties.java
@@ -0,0 +1,261 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2016 Symbian Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andrew Ferguson (Symbian) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * DBProperties is a bare-bones implementation of a String->String mapping. It is neither
+ * a Map or a Properties subclass, because of their more general applications.
+ */
+public class DBProperties {
+ static final int PROP_INDEX = 0;
+ static final int RECORD_SIZE = 4;
+
+ protected BTree index;
+ protected Database db;
+ protected long record;
+
+ /**
+ * Allocate storage for a new DBProperties record in the specified database
+ */
+ public DBProperties(Nd nd) throws IndexException {
+ Database database = nd.getDB();
+ this.record= database.malloc(RECORD_SIZE, Database.POOL_DB_PROPERTIES);
+ this.index= new BTree(nd, this.record + PROP_INDEX, DBProperty.getComparator());
+ this.db= database;
+ }
+
+ /**
+ * Creates an object for accessing an existing DBProperties record at the specified location
+ * of the specified database.
+ */
+ public DBProperties(Nd nd, long record) throws IndexException {
+ Database database = nd.getDB();
+ this.record= record;
+ this.index= new BTree(nd, record + PROP_INDEX, DBProperty.getComparator());
+ this.db= database;
+ }
+
+ /**
+ * Reads the named property from this properties storage.
+ * @param key a case-sensitive identifier for a property, or null
+ * @return the value associated with the key, or null if either no such property is set,
+ * or the specified key was null
+ * @throws IndexException
+ */
+ public String getProperty(String key) throws IndexException {
+ if (key != null) {
+ DBProperty existing= DBProperty.search(this.db, this.index, key);
+ if (existing != null) {
+ return existing.getValue().getString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Reads the named property from this properties storage, returning the default value if there
+ * is no such property.
+ * @param key a case-sensitive identifier for a property, or null
+ * @param defaultValue a value to return in case the specified key was null
+ * @return the value associated with the key, or the specified default value if either no such
+ * property is set, or the specified key was null
+ * @throws IndexException
+ */
+ public String getProperty(String key, String defaultValue) throws IndexException {
+ String val= getProperty(key);
+ return (val == null) ? defaultValue : val;
+ }
+
+ /**
+ * Returns the Set of property names stored in this object
+ * @return the Set of property names stored in this object
+ * @throws IndexException
+ */
+ public Set<String> getKeySet() throws IndexException {
+ return DBProperty.getKeySet(this.db, this.index);
+ }
+
+ /**
+ * Writes the key, value mapping to the properties. If a mapping for the
+ * same key already exists, it is overwritten.
+ * @param key a non-null property name
+ * @param value a value to associate with the key. may not be null.
+ * @throws IndexException
+ * @throws NullPointerException if key is null
+ */
+ public void setProperty(String key, String value) throws IndexException {
+ removeProperty(key);
+ DBProperty newProperty= new DBProperty(this.db, key, value);
+ this.index.insert(newProperty.getRecord());
+ }
+
+ /**
+ * Deletes a property from this DBProperties object.
+ * @param key
+ * @return whether a property with matching key existed and was removed, or false if the key
+ * was null
+ * @throws IndexException
+ */
+ public boolean removeProperty(String key) throws IndexException {
+ if (key != null) {
+ DBProperty existing= DBProperty.search(this.db, this.index, key);
+ if (existing != null) {
+ this.index.delete(existing.getRecord());
+ existing.delete();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Deletes all properties, does not delete the record associated with the object itself
+ * - that is it can be re-populated.
+ * @throws IndexException
+ */
+ public void clear() throws IndexException {
+ this.index.accept(new IBTreeVisitor(){
+ @Override
+ public int compare(long address) throws IndexException {
+ return 0;
+ }
+ @Override
+ public boolean visit(long address) throws IndexException {
+ new DBProperty(DBProperties.this.db, address).delete();
+ return false; // there should never be duplicates
+ }
+ });
+ }
+
+ /**
+ * Deletes all properties stored in this object and the record associated with this object
+ * itself.
+ * <br><br>
+ * <b>The behaviour of objects of this class after calling this method is undefined</b>
+ * @throws IndexException
+ */
+ public void delete() throws IndexException {
+ clear();
+ this.db.free(this.record, Database.POOL_DB_PROPERTIES);
+ }
+
+ public long getRecord() {
+ return this.record;
+ }
+
+ private static class DBProperty {
+ static final int KEY = 0;
+ static final int VALUE = 4;
+ @SuppressWarnings("hiding")
+ static final int RECORD_SIZE = 8;
+
+ Database db;
+ long record;
+
+ public long getRecord() {
+ return this.record;
+ }
+
+ /**
+ * Allocates and initializes a record in the specified database for a DBProperty record
+ * @param db
+ * @param key a non-null property name
+ * @param value a non-null property value
+ * @throws IndexException
+ */
+ DBProperty(Database db, String key, String value) throws IndexException {
+ assert key != null;
+ assert value != null;
+ IString dbkey= db.newString(key);
+ IString dbvalue= db.newString(value);
+ this.record= db.malloc(RECORD_SIZE, Database.POOL_DB_PROPERTIES);
+ db.putRecPtr(this.record + KEY, dbkey.getRecord());
+ db.putRecPtr(this.record + VALUE, dbvalue.getRecord());
+ this.db= db;
+ }
+
+ /**
+ * Returns an object for accessing an existing DBProperty record at the specified location
+ * in the specified database.
+ * @param db
+ * @param record
+ */
+ DBProperty(Database db, long record) {
+ this.record= record;
+ this.db= db;
+ }
+
+ public IString getKey() throws IndexException {
+ return this.db.getString(this.db.getRecPtr(this.record + KEY));
+ }
+
+ public IString getValue() throws IndexException {
+ return this.db.getString(this.db.getRecPtr(this.record + VALUE));
+ }
+
+ public static IBTreeComparator getComparator() {
+ return new IBTreeComparator() {
+ @Override
+ public int compare(Nd nd, long record1, long record2) throws IndexException {
+ Database db = nd.getDB();
+ IString left= db.getString(db.getRecPtr(record1 + KEY));
+ IString right= db.getString(db.getRecPtr(record2 + KEY));
+ return left.compare(right, true);
+ }
+ };
+ }
+
+ public static DBProperty search(final Database db, final BTree index, final String key) throws IndexException {
+ final DBProperty[] result= new DBProperty[1];
+ index.accept(new IBTreeVisitor(){
+ @Override
+ public int compare(long record) throws IndexException {
+ return db.getString(db.getRecPtr(record + KEY)).compare(key, true);
+ }
+
+ @Override
+ public boolean visit(long record) throws IndexException {
+ result[0] = new DBProperty(db, record);
+ return false; // There should never be duplicates.
+ }
+ });
+ return result[0];
+ }
+
+ public static Set<String> getKeySet(final Database db, final BTree index) throws IndexException {
+ final Set<String> result= new HashSet<String>();
+ index.accept(new IBTreeVisitor(){
+ @Override
+ public int compare(long record) throws IndexException {
+ return 0;
+ }
+
+ @Override
+ public boolean visit(long record) throws IndexException {
+ result.add(new DBProperty(db, record).getKey().getString());
+ return true; // There should never be duplicates.
+ }
+ });
+ return result;
+ }
+
+ public void delete() throws IndexException {
+ this.db.getString(this.db.getRecPtr(this.record + KEY)).delete();
+ this.db.getString(this.db.getRecPtr(this.record + VALUE)).delete();
+ this.db.free(this.record, Database.POOL_DB_PROPERTIES);
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBStatus.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBStatus.java
new file mode 100644
index 000000000..d881ff203
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/DBStatus.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.io.IOException;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+public class DBStatus extends Status {
+ /**
+ * @param exception
+ */
+ public DBStatus(IOException exception) {
+ super(IStatus.ERROR, Package.PLUGIN_ID, 0, "IOException", exception); //$NON-NLS-1$
+ }
+
+ public DBStatus(String msg) {
+ super(IStatus.ERROR, Package.PLUGIN_ID, 0, "Error", null); //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java
new file mode 100644
index 000000000..95b4b8dfa
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Database.java
@@ -0,0 +1,959 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ * Symbian - Add some non-javadoc implementation notes
+ * Markus Schorn (Wind River Systems)
+ * IBM Corporation
+ * Sergey Prigogin (Google)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+
+import com.ibm.icu.text.MessageFormat;
+
+/**
+ * Database encapsulates access to a flat binary format file with a memory-manager-like API for
+ * obtaining and releasing areas of storage (memory).
+ */
+/*
+ * The file encapsulated is divided into Chunks of size CHUNK_SIZE, and a table of contents
+ * mapping chunk index to chunk address is maintained. Chunk structure exists only conceptually -
+ * it is not a structure that appears in the file.
+ *
+ * ===== The first chunk is used by Database itself for house-keeping purposes and has structure
+ *
+ * offset content
+ * _____________________________
+ * 0 | version number
+ * INT_SIZE | pointer to head of linked list of blocks of size MIN_BLOCK_DELTAS*BLOCK_SIZE_DELTA
+ * .. | ...
+ * INT_SIZE * (M + 1) | pointer to head of linked list of blocks of size (M + MIN_BLOCK_DELTAS) * BLOCK_SIZE_DELTA
+ * WRITE_NUMBER_OFFSET | long integer which is incremented on every write
+ * MALLOC_STATS_OFFSET | memory usage statistics
+ * DATA_AREA | The database singletons are stored here and use the remainder of chunk 0
+ *
+ * M = CHUNK_SIZE / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS
+ *
+ * ===== block structure (for free/unused blocks)
+ *
+ * offset content
+ * _____________________________
+ * 0 | size of block (positive indicates an unused block) (2 bytes)
+ * PREV_OFFSET | pointer to previous block (of same size) (only in free blocks)
+ * NEXT_OFFSET | pointer to next block (of same size) (only in free blocks)
+ * ... | unused space
+ *
+ *====== block structure (for allocated blocks)
+ *
+ * offset content
+ * _____________________________
+ * 0 | size of block (negative indicates the block is in use) (2 bytes)
+ * 2 | content of the struct
+ *
+ */
+public class Database {
+ public static final int CHAR_SIZE = 2;
+ public static final int BYTE_SIZE = 1;
+ public static final int SHORT_SIZE = 2;
+ public static final int INT_SIZE = 4;
+ public static final int LONG_SIZE = 8;
+ public static final int CHUNK_SIZE = 1024 * 4;
+ public static final int OFFSET_IN_CHUNK_MASK= CHUNK_SIZE - 1;
+ public static final int BLOCK_HEADER_SIZE = SHORT_SIZE;
+
+ public static final int BLOCK_SIZE_DELTA_BITS = 3;
+ public static final int BLOCK_SIZE_DELTA= 1 << BLOCK_SIZE_DELTA_BITS;
+
+ // Fields that are only used by free blocks
+ private static final int BLOCK_PREV_OFFSET = BLOCK_HEADER_SIZE;
+ private static final int BLOCK_NEXT_OFFSET = BLOCK_HEADER_SIZE + INT_SIZE;
+ private static final int FREE_BLOCK_HEADER_SIZE = BLOCK_NEXT_OFFSET + INT_SIZE;
+
+ public static final int MIN_BLOCK_DELTAS = (FREE_BLOCK_HEADER_SIZE + BLOCK_SIZE_DELTA - 1) /
+ BLOCK_SIZE_DELTA; // Must be enough multiples of BLOCK_SIZE_DELTA in order to fit the free block header
+ public static final int MAX_BLOCK_DELTAS = CHUNK_SIZE / BLOCK_SIZE_DELTA;
+ public static final int MAX_MALLOC_SIZE = MAX_BLOCK_DELTAS * BLOCK_SIZE_DELTA - BLOCK_HEADER_SIZE;
+ public static final int PTR_SIZE = 4; // size of a pointer in the database in bytes
+ public static final int STRING_SIZE = PTR_SIZE;
+ public static final int FLOAT_SIZE = INT_SIZE;
+ public static final int DOUBLE_SIZE = LONG_SIZE;
+ public static final long MAX_DB_SIZE= ((long) 1 << (Integer.SIZE + BLOCK_SIZE_DELTA_BITS));
+
+ public static final int VERSION_OFFSET = 0;
+ private static final int MALLOC_TABLE_OFFSET = VERSION_OFFSET + INT_SIZE;
+ public static final int WRITE_NUMBER_OFFSET = MALLOC_TABLE_OFFSET
+ + (CHUNK_SIZE / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS + 1) * INT_SIZE;
+ public static final int MALLOC_STATS_OFFSET = WRITE_NUMBER_OFFSET + LONG_SIZE;
+ public static final int DATA_AREA_OFFSET = MALLOC_STATS_OFFSET + MemoryStats.SIZE;
+
+ // Malloc pool IDs (used for classifying memory allocations and recording statistics about them)
+ /** Misc pool -- may be used for any purpose that doesn't fit the IDs below. */
+ public static final short POOL_MISC = 0x0000;
+ public static final short POOL_BTREE = 0x0001;
+ public static final short POOL_DB_PROPERTIES = 0x0002;
+ public static final short POOL_STRING_LONG = 0x0003;
+ public static final short POOL_STRING_SHORT = 0x0004;
+ public static final short POOL_LINKED_LIST = 0x0005;
+ public static final short POOL_STRING_SET = 0x0006;
+ public static final short POOL_GROWABLE_ARRAY = 0x0007;
+ /** Id for the first node type. All node types will record their stats in a pool whose ID is POOL_FIRST_NODE_TYPE + node_id*/
+ public static final short POOL_FIRST_NODE_TYPE = 0x0100;
+
+ private final File fLocation;
+ private final boolean fReadOnly;
+ private RandomAccessFile fFile;
+ private boolean fExclusiveLock; // Necessary for any write operation.
+ private boolean fLocked; // Necessary for any operation.
+ private boolean fIsMarkedIncomplete;
+
+ private int fVersion;
+ private final Chunk fHeaderChunk;
+ private Chunk[] fChunks;
+ private int fChunksUsed;
+ private int fChunksAllocated;
+ private ChunkCache fCache;
+
+ private long malloced;
+ private long freed;
+ private long cacheHits;
+ private long cacheMisses;
+
+ private MemoryStats memoryUsage;
+
+ /**
+ * Construct a new Database object, creating a backing file if necessary.
+ * @param location the local file path for the database
+ * @param cache the cache to be used optimization
+ * @param version the version number to store in the database (only applicable for new databases)
+ * @param openReadOnly whether this Database object will ever need writing to
+ * @throws IndexException
+ */
+ public Database(File location, ChunkCache cache, int version, boolean openReadOnly) throws IndexException {
+ try {
+ this.fLocation = location;
+ this.fReadOnly= openReadOnly;
+ this.fCache= cache;
+ openFile();
+
+ int nChunksOnDisk = (int) (this.fFile.length() / CHUNK_SIZE);
+ this.fHeaderChunk= new Chunk(this, 0);
+ this.fHeaderChunk.fLocked= true; // Never makes it into the cache, needed to satisfy assertions.
+ if (nChunksOnDisk <= 0) {
+ this.fVersion= version;
+ this.fChunks= new Chunk[1];
+ this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+ } else {
+ this.fHeaderChunk.read();
+ this.fVersion= this.fHeaderChunk.getInt(VERSION_OFFSET);
+ this.fChunks = new Chunk[nChunksOnDisk]; // chunk[0] is unused.
+ this.fChunksUsed = this.fChunksAllocated = nChunksOnDisk;
+ }
+ } catch (IOException e) {
+ throw new IndexException(new DBStatus(e));
+ }
+ this.memoryUsage = new MemoryStats(this.fHeaderChunk, MALLOC_STATS_OFFSET);
+ }
+
+ private static int divideRoundingUp(int num, int den) {
+ return (num + den - 1) / den;
+ }
+
+ private void openFile() throws FileNotFoundException {
+ this.fFile = new RandomAccessFile(this.fLocation, this.fReadOnly ? "r" : "rw"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ void read(ByteBuffer buf, long position) throws IOException {
+ int retries= 0;
+ do {
+ try {
+ this.fFile.getChannel().read(buf, position);
+ return;
+ } catch (ClosedChannelException e) {
+ // Always reopen the file if possible or subsequent reads will fail.
+ openFile();
+
+ // This is the most common type of interruption. If another thread called Thread.interrupt,
+ // throw an OperationCanceledException.
+ if (e instanceof ClosedByInterruptException) {
+ throw new OperationCanceledException();
+ }
+
+ // If we've retried too many times, just rethrow the exception.
+ if (++retries >= 20) {
+ throw e;
+ }
+
+ // Otherwise, retry
+ }
+ } while (true);
+ }
+
+ /**
+ * Attempts to write to the given position in the file. Will retry if interrupted by Thread.interrupt() until,
+ * the write succeeds. It will return true if any call to Thread.interrupt() was detected.
+ *
+ * @return true iff a call to Thread.interrupt() was detected at any point during the operation.
+ * @throws IOException
+ */
+ boolean write(ByteBuffer buf, long position) throws IOException {
+ return performUninterruptableWrite(() -> {this.fFile.getChannel().write(buf, position);});
+ }
+
+ private static interface IORunnable {
+ void run() throws IOException;
+ }
+
+ /**
+ * Attempts to perform an uninterruptable write operation on the database. Returns true if an attempt was made
+ * to interrupt it.
+ *
+ * @throws IOException
+ */
+ private boolean performUninterruptableWrite(IORunnable runnable) throws IOException {
+ boolean interrupted = false;
+ int retries= 0;
+ while (true) {
+ try {
+ runnable.run();
+ return interrupted;
+ } catch (ClosedChannelException e) {
+ openFile();
+
+ if (e instanceof ClosedByInterruptException) {
+ // Retry forever if necessary as long as another thread is calling Thread.interrupt
+ interrupted = true;
+ } else {
+ if (++retries > 20) {
+ throw e;
+ }
+ }
+ }
+ }
+ }
+
+ public void transferTo(FileChannel target) throws IOException {
+ assert this.fLocked;
+ final FileChannel from= this.fFile.getChannel();
+ long nRead = 0;
+ long position = 0;
+ long size = from.size();
+ while (position < size) {
+ nRead = from.transferTo(position, 4096 * 16, target);
+ if (nRead == 0) {
+ break; // Should not happen.
+ } else {
+ position+= nRead;
+ }
+ }
+ }
+
+ public int getVersion() {
+ return this.fVersion;
+ }
+
+ public void setVersion(int version) throws IndexException {
+ assert this.fExclusiveLock;
+ this.fHeaderChunk.putInt(VERSION_OFFSET, version);
+ this.fVersion= version;
+ }
+
+ /**
+ * Empty the contents of the Database, make it ready to start again. Interrupting the thread with
+ * {@link Thread#interrupt()} won't interrupt the write. Returns true iff the thread was interrupted
+ * with {@link Thread#interrupt()}.
+ *
+ * @throws IndexException
+ */
+ public boolean clear(int version) throws IndexException {
+ assert this.fExclusiveLock;
+ boolean wasCanceled = false;
+ removeChunksFromCache();
+
+ this.fVersion= version;
+ // Clear the first chunk.
+ this.fHeaderChunk.clear(0, CHUNK_SIZE);
+ // Chunks have been removed from the cache, so we may just reset the array of chunks.
+ this.fChunks = new Chunk[] {null};
+ this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+ try {
+ wasCanceled = this.fHeaderChunk.flush() || wasCanceled; // Zero out header chunk.
+ wasCanceled = performUninterruptableWrite(() -> {
+ this.fFile.getChannel().truncate(CHUNK_SIZE);
+ }) || wasCanceled;
+ } catch (IOException e) {
+ Package.log(e);
+ }
+ this.malloced = this.freed = 0;
+ /*
+ * This is for debugging purposes in order to simulate having a very large Nd database.
+ * This will set aside the specified number of chunks.
+ * Nothing uses these chunks so subsequent allocations come after these fillers.
+ * The special function createNewChunks allocates all of these chunks at once.
+ * 524288 for a file starting at 2G
+ * 8388608 for a file starting at 32G
+ *
+ */
+ long setasideChunks = Long.getLong("org.eclipse.jdt.core.parser.nd.chunks", 0); //$NON-NLS-1$
+ if (setasideChunks != 0) {
+ setVersion(getVersion());
+ createNewChunks((int) setasideChunks);
+ wasCanceled = flush() || wasCanceled;
+ }
+ this.memoryUsage.refresh();
+ return wasCanceled;
+ }
+
+ private void removeChunksFromCache() {
+ synchronized (this.fCache) {
+ for (int i= 1; i < this.fChunks.length; i++) {
+ Chunk chunk= this.fChunks[i];
+ if (chunk != null) {
+ this.fCache.remove(chunk);
+ this.fChunks[i]= null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the Chunk that contains the given offset.
+ * @throws IndexException
+ */
+ public Chunk getChunk(long offset) throws IndexException {
+ assertLocked();
+ if (offset < CHUNK_SIZE) {
+ return this.fHeaderChunk;
+ }
+ long long_index = offset / CHUNK_SIZE;
+ assert long_index < Integer.MAX_VALUE;
+
+ synchronized (this.fCache) {
+ assert this.fLocked;
+ final int index = (int) long_index;
+ if (index < 0 || index >= this.fChunks.length) {
+ databaseCorruptionDetected();
+ }
+ Chunk chunk= this.fChunks[index];
+ if (chunk == null) {
+ this.cacheMisses++;
+ chunk = new Chunk(this, index);
+ chunk.read();
+ this.fChunks[index] = chunk;
+ } else {
+ this.cacheHits++;
+ }
+ this.fCache.add(chunk, this.fExclusiveLock);
+ return chunk;
+ }
+ }
+
+ public void assertLocked() {
+ if (!this.fLocked) {
+ throw new IllegalStateException("Database not locked!"); //$NON-NLS-1$
+ }
+ }
+
+ private void databaseCorruptionDetected() throws IndexException {
+ String msg = MessageFormat.format("Corrupted database: {0}", //$NON-NLS-1$
+ new Object[] { this.fLocation.getName() });
+ throw new IndexException(new DBStatus(msg));
+ }
+
+ /**
+ * Copies numBytes from source to destination
+ */
+ public void memcpy(long dest, long source, int numBytes) {
+ assert numBytes >= 0;
+ assert numBytes <= MAX_MALLOC_SIZE;
+ // TODO: make use of lower-level System.arrayCopy
+ for (int count = 0; count < numBytes; count++) {
+ putByte(dest + count, getByte(source + count));
+ }
+ }
+
+ /**
+ * Allocate a block out of the database.
+ */
+ public long malloc(final int datasize, final short poolId) throws IndexException {
+ assert this.fExclusiveLock;
+ assert datasize >= 0;
+ assert datasize <= MAX_MALLOC_SIZE;
+
+ int needDeltas= divideRoundingUp(datasize + BLOCK_HEADER_SIZE, BLOCK_SIZE_DELTA);
+ if (needDeltas < MIN_BLOCK_DELTAS) {
+ needDeltas= MIN_BLOCK_DELTAS;
+ }
+
+ // Which block size.
+ long freeblock = 0;
+ int useDeltas;
+ for (useDeltas= needDeltas; useDeltas <= MAX_BLOCK_DELTAS; useDeltas++) {
+ freeblock = getFirstBlock(useDeltas * BLOCK_SIZE_DELTA);
+ if (freeblock != 0)
+ break;
+ }
+
+ // Get the block.
+ Chunk chunk;
+ if (freeblock == 0) {
+ // Allocate a new chunk.
+ freeblock= createNewChunk();
+ useDeltas = MAX_BLOCK_DELTAS;
+ chunk = getChunk(freeblock);
+ } else {
+ chunk = getChunk(freeblock);
+ removeBlock(chunk, useDeltas * BLOCK_SIZE_DELTA, freeblock);
+ }
+
+ final int unusedDeltas = useDeltas - needDeltas;
+ if (unusedDeltas >= MIN_BLOCK_DELTAS) {
+ // Add in the unused part of our block.
+ addBlock(chunk, unusedDeltas * BLOCK_SIZE_DELTA, freeblock + needDeltas * BLOCK_SIZE_DELTA);
+ useDeltas= needDeltas;
+ }
+
+ // Make our size negative to show in use.
+ final int usedSize= useDeltas * BLOCK_SIZE_DELTA;
+ chunk.putShort(freeblock, (short) -usedSize);
+
+ // Clear out the block, lots of people are expecting this.
+ chunk.clear(freeblock + BLOCK_HEADER_SIZE, usedSize - BLOCK_HEADER_SIZE);
+
+ this.malloced += usedSize;
+ long result = freeblock + BLOCK_HEADER_SIZE;
+ this.memoryUsage.recordMalloc(poolId, usedSize);
+ return result;
+ }
+
+ private long createNewChunk() throws IndexException {
+ assert this.fExclusiveLock;
+ synchronized (this.fCache) {
+ final int newChunkIndex = this.fChunksUsed; // fChunks.length;
+
+ final Chunk chunk = new Chunk(this, newChunkIndex);
+ chunk.fDirty = true;
+
+ if (newChunkIndex >= this.fChunksAllocated) {
+ int increment = Math.max(1024, this.fChunksAllocated / 20);
+ Chunk[] newchunks = new Chunk[this.fChunksAllocated + increment];
+ System.arraycopy(this.fChunks, 0, newchunks, 0, this.fChunksAllocated);
+
+ this.fChunks = newchunks;
+ this.fChunksAllocated += increment;
+ }
+ this.fChunksUsed += 1;
+ this.fChunks[newChunkIndex] = chunk;
+
+ this.fCache.add(chunk, true);
+ long address = (long) newChunkIndex * CHUNK_SIZE;
+
+ /*
+ * Non-dense pointers are at most 31 bits dense pointers are at most 35 bits Check the sizes here and throw
+ * an exception if the address is too large. By throwing the IndexException with the special status, the
+ * indexing operation should be stopped. This is desired since generally, once the max size is exceeded,
+ * there are lots of errors.
+ */
+ if (address >= MAX_DB_SIZE) {
+ Object bindings[] = { this.getLocation().getAbsolutePath(), MAX_DB_SIZE };
+ throw new IndexException(new Status(IStatus.ERROR, Package.PLUGIN_ID, Package.STATUS_DATABASE_TOO_LARGE,
+ NLS.bind("Database too large! Address = " + address + ", max size = " + MAX_DB_SIZE, bindings), //$NON-NLS-1$ //$NON-NLS-2$
+ null));
+ }
+ return address;
+ }
+ }
+
+ /**
+ * For testing purposes, only.
+ */
+ private long createNewChunks(int numChunks) throws IndexException {
+ assert this.fExclusiveLock;
+ synchronized (this.fCache) {
+ final int oldLen= this.fChunks.length;
+ Chunk[] newchunks = new Chunk[oldLen + numChunks];
+ System.arraycopy(this.fChunks, 0, newchunks, 0, oldLen);
+ final Chunk chunk= new Chunk(this, oldLen + numChunks - 1);
+ chunk.fDirty= true;
+ newchunks[ oldLen + numChunks - 1 ] = chunk;
+ this.fChunks= newchunks;
+ this.fCache.add(chunk, true);
+ this.fChunksAllocated=oldLen + numChunks;
+ this.fChunksUsed=oldLen + numChunks;
+ return (long) (oldLen + numChunks - 1) * CHUNK_SIZE;
+ }
+ }
+
+ /**
+ * @param blocksize (must be a multiple of BLOCK_SIZE_DELTA)
+ */
+ private long getFirstBlock(int blocksize) throws IndexException {
+ assert this.fLocked;
+ return this.fHeaderChunk.getFreeRecPtr(MALLOC_TABLE_OFFSET + (blocksize / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS) * INT_SIZE);
+ }
+
+ private void setFirstBlock(int blocksize, long block) throws IndexException {
+ assert this.fExclusiveLock;
+ this.fHeaderChunk.putFreeRecPtr(MALLOC_TABLE_OFFSET + (blocksize / BLOCK_SIZE_DELTA - MIN_BLOCK_DELTAS) * INT_SIZE, block);
+ }
+
+ private void removeBlock(Chunk chunk, int blocksize, long block) throws IndexException {
+ assert this.fExclusiveLock;
+ long prevblock = chunk.getFreeRecPtr(block + BLOCK_PREV_OFFSET);
+ long nextblock = chunk.getFreeRecPtr(block + BLOCK_NEXT_OFFSET);
+ if (prevblock != 0) {
+ putFreeRecPtr(prevblock + BLOCK_NEXT_OFFSET, nextblock);
+ } else { // We were the head.
+ setFirstBlock(blocksize, nextblock);
+ }
+
+ if (nextblock != 0)
+ putFreeRecPtr(nextblock + BLOCK_PREV_OFFSET, prevblock);
+ }
+
+ private void addBlock(Chunk chunk, int blocksize, long block) throws IndexException {
+ assert this.fExclusiveLock;
+ // Mark our size
+ chunk.putShort(block, (short) blocksize);
+
+ // Add us to the head of the list.
+ long prevfirst = getFirstBlock(blocksize);
+ chunk.putFreeRecPtr(block + BLOCK_PREV_OFFSET, 0);
+ chunk.putFreeRecPtr(block + BLOCK_NEXT_OFFSET, prevfirst);
+ if (prevfirst != 0)
+ putFreeRecPtr(prevfirst + BLOCK_PREV_OFFSET, block);
+ setFirstBlock(blocksize, block);
+ }
+
+ /**
+ * Free an allocated block.
+ *
+ * @param address memory address to be freed
+ * @param poolId the same ID that was previously passed into malloc when allocating this memory address
+ */
+ public void free(long address, short poolId) throws IndexException {
+ assert this.fExclusiveLock;
+ if (address == 0) {
+ return;
+ }
+ // TODO Look for opportunities to merge blocks
+ long block = address - BLOCK_HEADER_SIZE;
+ Chunk chunk = getChunk(block);
+ int blocksize = - chunk.getShort(block);
+ if (blocksize < 0) {
+ // Already freed.
+ throw new IndexException(new Status(IStatus.ERROR, Package.PLUGIN_ID, 0,
+ "Already freed record " + address, new Exception())); //$NON-NLS-1$
+ }
+ addBlock(chunk, blocksize, block);
+ this.freed += blocksize;
+ this.memoryUsage.recordFree(poolId, blocksize);
+ }
+
+ public void putByte(long offset, byte value) throws IndexException {
+ getChunk(offset).putByte(offset, value);
+ }
+
+ public byte getByte(long offset) throws IndexException {
+ return getChunk(offset).getByte(offset);
+ }
+
+ public void putInt(long offset, int value) throws IndexException {
+ getChunk(offset).putInt(offset, value);
+ }
+
+ public int getInt(long offset) throws IndexException {
+ return getChunk(offset).getInt(offset);
+ }
+
+ public void putRecPtr(long offset, long value) throws IndexException {
+ getChunk(offset).putRecPtr(offset, value);
+ }
+
+ public long getRecPtr(long offset) throws IndexException {
+ return getChunk(offset).getRecPtr(offset);
+ }
+
+ private void putFreeRecPtr(long offset, long value) throws IndexException {
+ getChunk(offset).putFreeRecPtr(offset, value);
+ }
+
+ private long getFreeRecPtr(long offset) throws IndexException {
+ return getChunk(offset).getFreeRecPtr(offset);
+ }
+
+ public void put3ByteUnsignedInt(long offset, int value) throws IndexException {
+ getChunk(offset).put3ByteUnsignedInt(offset, value);
+ }
+
+ public int get3ByteUnsignedInt(long offset) throws IndexException {
+ return getChunk(offset).get3ByteUnsignedInt(offset);
+ }
+
+ public void putShort(long offset, short value) throws IndexException {
+ getChunk(offset).putShort(offset, value);
+ }
+
+ public short getShort(long offset) throws IndexException {
+ return getChunk(offset).getShort(offset);
+ }
+
+ public void putLong(long offset, long value) throws IndexException {
+ getChunk(offset).putLong(offset, value);
+ }
+
+ public void putDouble(long offset, double value) throws IndexException {
+ getChunk(offset).putDouble(offset, value);
+ }
+
+ public void putFloat(long offset, float value) throws IndexException {
+ getChunk(offset).putFloat(offset, value);
+ }
+
+ public long getLong(long offset) throws IndexException {
+ return getChunk(offset).getLong(offset);
+ }
+
+ public double getDouble(long offset) throws IndexException {
+ return getChunk(offset).getDouble(offset);
+ }
+
+ public float getFloat(long offset) throws IndexException {
+ return getChunk(offset).getFloat(offset);
+ }
+
+ public void putChar(long offset, char value) throws IndexException {
+ getChunk(offset).putChar(offset, value);
+ }
+
+ public char getChar(long offset) throws IndexException {
+ return getChunk(offset).getChar(offset);
+ }
+
+ public void clearBytes(long offset, int byteCount) throws IndexException {
+ getChunk(offset).clear(offset, byteCount);
+ }
+
+ public void putBytes(long offset, byte[] data, int len) throws IndexException {
+ getChunk(offset).put(offset, data, len);
+ }
+
+ public void putBytes(long offset, byte[] data, int dataPos, int len) throws IndexException {
+ getChunk(offset).put(offset, data, dataPos, len);
+ }
+
+ public void getBytes(long offset, byte[] data) throws IndexException {
+ getChunk(offset).get(offset, data);
+ }
+
+ public void getBytes(long offset, byte[] data, int dataPos, int len) throws IndexException {
+ getChunk(offset).get(offset, data, dataPos, len);
+ }
+
+ public IString newString(String string) throws IndexException {
+ return newString(string.toCharArray());
+ }
+
+ public IString newString(char[] chars) throws IndexException {
+ int len= chars.length;
+ int bytelen;
+ final boolean useBytes = useBytes(chars);
+ if (useBytes) {
+ bytelen= len;
+ } else {
+ bytelen= 2 * len;
+ }
+
+ if (bytelen > ShortString.MAX_BYTE_LENGTH) {
+ return new LongString(this, chars, useBytes);
+ } else {
+ return new ShortString(this, chars, useBytes);
+ }
+ }
+
+ private boolean useBytes(char[] chars) {
+ for (char c : chars) {
+ if ((c & 0xff00) != 0)
+ return false;
+ }
+ return true;
+ }
+
+ public IString getString(long offset) throws IndexException {
+ final int l = getInt(offset);
+ int bytelen= l < 0 ? -l : 2 * l;
+ if (bytelen > ShortString.MAX_BYTE_LENGTH) {
+ return new LongString(this, offset);
+ }
+ return new ShortString(this, offset);
+ }
+
+ public long getDatabaseSize() {
+ return this.fChunksUsed * CHUNK_SIZE;
+ }
+
+ /**
+ * For debugging purposes, only.
+ */
+ public void reportFreeBlocks() throws IndexException {
+ System.out.println("Allocated size: " + getDatabaseSize() + " bytes"); //$NON-NLS-1$ //$NON-NLS-2$
+ System.out.println("malloc'ed: " + this.malloced); //$NON-NLS-1$
+ System.out.println("free'd: " + this.freed); //$NON-NLS-1$
+ System.out.println("wasted: " + (getDatabaseSize() - (this.malloced - this.freed))); //$NON-NLS-1$
+ System.out.println("Free blocks"); //$NON-NLS-1$
+ for (int bs = MIN_BLOCK_DELTAS*BLOCK_SIZE_DELTA; bs <= CHUNK_SIZE; bs += BLOCK_SIZE_DELTA) {
+ int count = 0;
+ long block = getFirstBlock(bs);
+ while (block != 0) {
+ ++count;
+ block = getFreeRecPtr(block + BLOCK_NEXT_OFFSET);
+ }
+ if (count != 0)
+ System.out.println("Block size: " + bs + "=" + count); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ /**
+ * Closes the database.
+ * <p>
+ * The behavior of any further calls to the Database is undefined
+ * @throws IndexException
+ */
+ public void close() throws IndexException {
+ assert this.fExclusiveLock;
+ flush();
+ removeChunksFromCache();
+
+ // Chunks have been removed from the cache, so we are fine.
+ this.fHeaderChunk.clear(0, CHUNK_SIZE);
+ this.memoryUsage.refresh();
+ this.fHeaderChunk.fDirty= false;
+ this.fChunks= new Chunk[] { null };
+ this.fChunksUsed = this.fChunksAllocated = this.fChunks.length;
+ try {
+ this.fFile.close();
+ } catch (IOException e) {
+ throw new IndexException(new DBStatus(e));
+ }
+ }
+
+ /**
+ * This method is public for testing purposes only.
+ */
+ public File getLocation() {
+ return this.fLocation;
+ }
+
+ /**
+ * Called from any thread via the cache, protected by {@link #fCache}.
+ */
+ void releaseChunk(final Chunk chunk) {
+ if (!chunk.fLocked) {
+ this.fChunks[chunk.fSequenceNumber]= null;
+ }
+ }
+
+ /**
+ * Returns the cache used for this database.
+ * @since 4.0
+ */
+ public ChunkCache getChunkCache() {
+ return this.fCache;
+ }
+
+ /**
+ * Asserts that database is used by one thread exclusively. This is necessary when doing
+ * write operations.
+ */
+ public void setExclusiveLock() {
+ this.fExclusiveLock= true;
+ this.fLocked= true;
+ }
+
+ public void setLocked(boolean val) {
+ this.fLocked= val;
+ }
+
+ public boolean giveUpExclusiveLock(final boolean flush) throws IndexException {
+ boolean wasInterrupted = false;
+ if (this.fExclusiveLock) {
+ try {
+ ArrayList<Chunk> dirtyChunks= new ArrayList<>();
+ synchronized (this.fCache) {
+ for (int i= 1; i < this.fChunksUsed; i++) {
+ Chunk chunk= this.fChunks[i];
+ if (chunk != null) {
+ if (chunk.fCacheIndex < 0) {
+ // Locked chunk that has been removed from cache.
+ if (chunk.fDirty) {
+ dirtyChunks.add(chunk); // Keep in fChunks until it is flushed.
+ } else {
+ chunk.fLocked= false;
+ this.fChunks[i]= null;
+ }
+ } else if (chunk.fLocked) {
+ // Locked chunk, still in cache.
+ if (chunk.fDirty) {
+ if (flush) {
+ dirtyChunks.add(chunk);
+ }
+ } else {
+ chunk.fLocked= false;
+ }
+ } else {
+ assert !chunk.fDirty; // Dirty chunks must be locked.
+ }
+ }
+ }
+ }
+ // Also handles header chunk.
+ wasInterrupted = flushAndUnlockChunks(dirtyChunks, flush) || wasInterrupted;
+ } finally {
+ this.fExclusiveLock= false;
+ }
+ }
+ return wasInterrupted;
+ }
+
+ public boolean flush() throws IndexException {
+ boolean wasInterrupted = false;
+ assert this.fLocked;
+ if (this.fExclusiveLock) {
+ try {
+ wasInterrupted = giveUpExclusiveLock(true) || wasInterrupted;
+ } finally {
+ setExclusiveLock();
+ }
+ return wasInterrupted;
+ }
+
+ // Be careful as other readers may access chunks concurrently.
+ ArrayList<Chunk> dirtyChunks= new ArrayList<>();
+ synchronized (this.fCache) {
+ for (int i= 1; i < this.fChunksUsed ; i++) {
+ Chunk chunk= this.fChunks[i];
+ if (chunk != null && chunk.fDirty) {
+ dirtyChunks.add(chunk);
+ }
+ }
+ }
+
+ // Also handles header chunk.
+ return flushAndUnlockChunks(dirtyChunks, true) || wasInterrupted;
+ }
+
+ /**
+ * Interrupting the thread with {@link Thread#interrupt()} won't interrupt the write. Returns true iff an attempt
+ * was made to interrupt the thread with {@link Thread#interrupt()}.
+ *
+ * @throws IndexException
+ */
+ private boolean flushAndUnlockChunks(final ArrayList<Chunk> dirtyChunks, boolean isComplete) throws IndexException {
+ boolean wasInterrupted = false;
+ assert !Thread.holdsLock(this.fCache);
+ synchronized (this.fHeaderChunk) {
+ final boolean haveDirtyChunks = !dirtyChunks.isEmpty();
+ if (haveDirtyChunks || this.fHeaderChunk.fDirty) {
+ wasInterrupted = markFileIncomplete() || wasInterrupted;
+ }
+ if (haveDirtyChunks) {
+ for (Chunk chunk : dirtyChunks) {
+ if (chunk.fDirty) {
+ wasInterrupted = chunk.flush() || wasInterrupted;
+ }
+ }
+
+ // Only after the chunks are flushed we may unlock and release them.
+ synchronized (this.fCache) {
+ for (Chunk chunk : dirtyChunks) {
+ chunk.fLocked= false;
+ if (chunk.fCacheIndex < 0) {
+ this.fChunks[chunk.fSequenceNumber]= null;
+ }
+ }
+ }
+ }
+
+ if (isComplete) {
+ if (this.fHeaderChunk.fDirty || this.fIsMarkedIncomplete) {
+ this.fHeaderChunk.putInt(VERSION_OFFSET, this.fVersion);
+ wasInterrupted = this.fHeaderChunk.flush() || wasInterrupted;
+ this.fIsMarkedIncomplete= false;
+ }
+ }
+ }
+ return wasInterrupted;
+ }
+
+ private boolean markFileIncomplete() throws IndexException {
+ boolean wasInterrupted = false;
+ if (!this.fIsMarkedIncomplete) {
+ this.fIsMarkedIncomplete= true;
+ try {
+ final ByteBuffer buf= ByteBuffer.wrap(new byte[4]);
+ wasInterrupted = performUninterruptableWrite(() -> this.fFile.getChannel().write(buf, 0));
+ } catch (IOException e) {
+ throw new IndexException(new DBStatus(e));
+ }
+ }
+ return wasInterrupted;
+ }
+
+ public void resetCacheCounters() {
+ this.cacheHits= this.cacheMisses= 0;
+ }
+
+ public long getCacheHits() {
+ return this.cacheHits;
+ }
+
+ public long getCacheMisses() {
+ return this.cacheMisses;
+ }
+
+ public long getSizeBytes() throws IOException {
+ return this.fFile.length();
+ }
+
+ /**
+ * A Record Pointer is a pointer as returned by Database.malloc().
+ * This is a pointer to a block + BLOCK_HEADER_SIZE.
+ */
+ public static void putRecPtr(final long value, byte[] buffer, int idx) {
+ final int denseValue = value == 0 ? 0 : Chunk.compressFreeRecPtr(value - BLOCK_HEADER_SIZE);
+ Chunk.putInt(denseValue, buffer, idx);
+ }
+
+ /**
+ * A Record Pointer is a pointer as returned by Database.malloc().
+ * This is a pointer to a block + BLOCK_HEADER_SIZE.
+ */
+ public static long getRecPtr(byte[] buffer, final int idx) {
+ int value = Chunk.getInt(buffer, idx);
+ long address = Chunk.expandToFreeRecPtr(value);
+ return address != 0 ? (address + BLOCK_HEADER_SIZE) : address;
+ }
+
+ public MemoryStats getMemoryStats() {
+ return this.memoryUsage;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/EmptyString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/EmptyString.java
new file mode 100644
index 000000000..f32fdb552
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/EmptyString.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+/**
+ * Represents an empty string.
+ */
+public class EmptyString implements IString {
+
+ private int compareResult;
+ private static EmptyString theEmptyString = new EmptyString();
+
+ private EmptyString() {
+ this.compareResult = "".compareTo("a"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ public static EmptyString create() {
+ return theEmptyString;
+ }
+
+ @Override
+ public long getRecord() {
+ return 0;
+ }
+
+ @Override
+ public int compare(IString string, boolean caseSensitive) {
+ if (string.length() == 0) {
+ return 0;
+ }
+ return this.compareResult;
+ }
+
+ @Override
+ public int compare(String string, boolean caseSensitive) {
+ if (string.length() == 0) {
+ return 0;
+ }
+ return this.compareResult;
+ }
+
+ @Override
+ public int compare(char[] chars, boolean caseSensitive) {
+ if (chars.length == 0) {
+ return 0;
+ }
+ return this.compareResult;
+ }
+
+ @Override
+ public int compareCompatibleWithIgnoreCase(IString string) {
+ if (string.length() == 0) {
+ return 0;
+ }
+ return this.compareResult;
+ }
+
+ @Override
+ public int compareCompatibleWithIgnoreCase(char[] chars) {
+ if (chars.length == 0) {
+ return 0;
+ }
+ return this.compareResult;
+ }
+
+ @Override
+ public int comparePrefix(char[] name, boolean caseSensitive) {
+ if (name.length == 0) {
+ return 0;
+ }
+ return this.compareResult;
+ }
+
+ @Override
+ public char[] getChars() {
+ return new char[0];
+ }
+
+ @Override
+ public String getString() {
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public void delete() {
+ // Can't be deleted
+ }
+
+ @Override
+ public int length() {
+ return 0;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeComparator.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeComparator.java
new file mode 100644
index 000000000..b2ab084cb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeComparator.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+public interface IBTreeComparator {
+ /**
+ * Compare two records. Used for insert.
+ */
+ public abstract int compare(Nd nd, long record1, long record2);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeVisitor.java
new file mode 100644
index 000000000..e0a28238f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IBTreeVisitor.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ * Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+/**
+ * The visitor visits all records where compare returns 0.
+ */
+public interface IBTreeVisitor {
+ /**
+ * Compare the record against an internally held key. The comparison must be
+ * compatible with the one used for the btree.
+ * Used for visiting.
+ *
+ * @param record
+ * @return -1 if record < key, 0 if record == key, 1 if record > key
+ * @throws IndexException
+ */
+ public abstract int compare(long record) throws IndexException;
+
+ /**
+ * Visit a given record and return whether to continue or not.
+
+ * @return <code>true</code> to continue the visit, <code>false</code> to abort it.
+ * @throws IndexException
+ */
+ public abstract boolean visit(long record) throws IndexException;
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IString.java
new file mode 100644
index 000000000..84079794c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IString.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ * Andrew Ferguson (Symbian)
+ * Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+/**
+ * Interface for strings stored in the database. There is more than one string
+ * format. This interface hides that fact.
+ *
+ * @author Doug Schaefer
+ */
+public interface IString {
+ /**
+ * Get the offset of this IString record in the Nd
+ */
+ public long getRecord();
+
+ // strcmp equivalents
+ /**
+ * Compare this IString record and the specified IString record
+ * @param string
+ * @param caseSensitive whether to compare in a case-sensitive way
+ * @return <ul><li> -1 if this &lt; string
+ * <li> 0 if this == string
+ * <li> 1 if this &gt; string
+ * </ul>
+ * @throws IndexException
+ */
+ public int compare(IString string, boolean caseSensitive) throws IndexException;
+
+ /**
+ * Compare this IString record and the specified String object
+ * @param string
+ * @param caseSensitive whether to compare in a case-sensitive way
+ * @return <ul><li> -1 if this &lt; string
+ * <li> 0 if this == string
+ * <li> 1 if this &gt; string
+ * </ul>
+ * @throws IndexException
+ */
+ public int compare(String string, boolean caseSensitive) throws IndexException;
+
+ /**
+ * Compare this IString record and the specified character array
+ * @param chars
+ * @param caseSensitive whether to compare in a case-sensitive way
+ * @return <ul><li> -1 if this &lt; chars
+ * <li> 0 if this == chars
+ * <li> 1 if this &gt; chars
+ * </ul>
+ * @throws IndexException
+ */
+ public int compare(char[] chars, boolean caseSensitive) throws IndexException;
+
+ /**
+ * Compare this IString record and the specified IString record in a case sensitive manner
+ * such that it is compatible with case insensitive comparison.
+ * @param string
+ * @return <ul><li> -1 if this &lt; string
+ * <li> 0 if this == string
+ * <li> 1 if this &gt; string
+ * </ul>
+ * @throws IndexException
+ */
+ public int compareCompatibleWithIgnoreCase(IString string) throws IndexException;
+
+ /**
+ * Compare this IString record and the specified char array in a case sensitive manner
+ * such that it is compatible with case insensitive comparison.
+ * @param chars
+ * @return <ul><li> -1 if this &lt; string
+ * <li> 0 if this == string
+ * <li> 1 if this &gt; string
+ * </ul>
+ * @throws IndexException
+ */
+ public int compareCompatibleWithIgnoreCase(char[] chars) throws IndexException;
+
+ /**
+ * Compare this IString record and the specified character array
+ * @param name the name to compare to
+ * @param caseSensitive whether to compare in a case-sensitive way
+ * @return <ul><li> -1 if this &lt; chars
+ * <li> 0 if this has a prefix chars
+ * <li> 1 if this &gt; chars and does not have the prefix
+ * </ul>
+ * @throws IndexException
+ */
+ public int comparePrefix(char[] name, boolean caseSensitive) throws IndexException;
+
+ /**
+ * Get an equivalent character array to this IString record<p>
+ * <b>N.B. This method can be expensive: compare and equals can be used for
+ * efficient comparisons</b>
+ * @return an equivalent character array to this IString record
+ * @throws IndexException
+ */
+ public char[] getChars() throws IndexException;
+
+ /**
+ * Get an equivalent String object to this IString record<p>
+ * <b>N.B. This method can be expensive: compare and equals can be used for
+ * efficient comparisons</b>
+ * @return an equivalent String object to this IString record
+ * @throws IndexException
+ */
+ public String getString() throws IndexException;
+
+ /**
+ * Free the associated record in the Nd
+ * @throws IndexException
+ */
+ public void delete() throws IndexException;
+
+ /**
+ * @return the length of the string
+ */
+ public int length();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java
new file mode 100644
index 000000000..f6ecf9a79
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/IndexException.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * This exception indicates corruption in the JDT index database.
+ */
+public class IndexException extends RuntimeException {
+
+ private IStatus status;
+
+ public IndexException(IStatus status) {
+ super(status.getMessage());
+ this.status = status;
+ }
+
+ public IndexException(String message) {
+ this(new Status(IStatus.ERROR, "org.eclipse.jdt.core", message)); //$NON-NLS-1$
+ }
+
+ @Override
+ public synchronized Throwable getCause() {
+ return this.status.getException();
+ }
+
+ /**
+ * @return the status
+ */
+ public IStatus getStatus() {
+ return this.status;
+ }
+
+ private static final long serialVersionUID = -6561893929558916225L;
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LongString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LongString.java
new file mode 100644
index 000000000..c78b7f909
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/LongString.java
@@ -0,0 +1,245 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ * Andrew Ferguson (Symbian)
+ * Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+/**
+ * This is for strings that take up more than on chunk.
+ * The string will need to be broken up into sections and then
+ * reassembled when necessary.
+ *
+ * @author Doug Schaefer
+ */
+public class LongString implements IString {
+ private final Database db;
+ private final long record;
+ private int hash;
+
+ // Additional fields of first record.
+ private static final int LENGTH = 0; // Must be first to match ShortString.
+ private static final int NEXT1 = 4;
+ private static final int CHARS1 = 8;
+
+ private static final int NUM_CHARS1 = (Database.MAX_MALLOC_SIZE - CHARS1) / 2;
+
+ // Additional fields of subsequent records.
+ private static final int NEXTN = 0;
+ private static final int CHARSN = 4;
+
+ private static final int NUM_CHARSN = (Database.MAX_MALLOC_SIZE - CHARSN) / 2;
+
+ public LongString(Database db, long record) {
+ this.db = db;
+ this.record = record;
+ }
+
+ public LongString(Database db, final char[] chars, boolean useBytes) throws IndexException {
+ final int numChars1 = useBytes ? NUM_CHARS1 * 2 : NUM_CHARS1;
+ final int numCharsn = useBytes ? NUM_CHARSN * 2 : NUM_CHARSN;
+
+ this.db = db;
+ this.record = db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_STRING_LONG);
+
+ // Write the first record.
+ final int length = chars.length;
+ db.putInt(this.record, useBytes ? -length : length);
+ Chunk chunk= db.getChunk(this.record);
+
+ if (useBytes) {
+ chunk.putCharsAsBytes(this.record + CHARS1, chars, 0, numChars1);
+ } else {
+ chunk.putChars(this.record + CHARS1, chars, 0, numChars1);
+ }
+
+ // Write the subsequent records.
+ long lastNext = this.record + NEXT1;
+ int start = numChars1;
+ while (length - start > numCharsn) {
+ long nextRecord = db.malloc(Database.MAX_MALLOC_SIZE, Database.POOL_STRING_LONG);
+ db.putRecPtr(lastNext, nextRecord);
+ chunk= db.getChunk(nextRecord);
+ if (useBytes) {
+ chunk.putCharsAsBytes(nextRecord + CHARSN, chars, start, numCharsn);
+ } else {
+ chunk.putChars(nextRecord + CHARSN, chars, start, numCharsn);
+ }
+ start += numCharsn;
+ lastNext = nextRecord + NEXTN;
+ }
+
+ // Write the last record.
+ int remaining= length - start;
+ long nextRecord = db.malloc(CHARSN + (useBytes ? remaining : remaining * 2), Database.POOL_STRING_LONG);
+ db.putRecPtr(lastNext, nextRecord);
+ chunk= db.getChunk(nextRecord);
+ if (useBytes) {
+ chunk.putCharsAsBytes(nextRecord + CHARSN, chars, start, remaining);
+ } else {
+ chunk.putChars(nextRecord + CHARSN, chars, start, remaining);
+ }
+ }
+
+ @Override
+ public long getRecord() {
+ return this.record;
+ }
+
+ @Override
+ public char[] getChars() throws IndexException {
+ int length = this.db.getInt(this.record + LENGTH);
+ final boolean useBytes = length < 0;
+ int numChars1 = NUM_CHARS1;
+ int numCharsn = NUM_CHARSN;
+ if (useBytes) {
+ length= -length;
+ numChars1 *= 2;
+ numCharsn *= 2;
+ }
+
+ final char[] chars = new char[length];
+
+ // First record
+ long p = this.record;
+ Chunk chunk= this.db.getChunk(p);
+ if (useBytes) {
+ chunk.getCharsFromBytes(p + CHARS1, chars, 0, numChars1);
+ } else {
+ chunk.getChars(p + CHARS1, chars, 0, numChars1);
+ }
+
+ int start= numChars1;
+ p= this.record + NEXT1;
+
+ // Other records
+ while (start < length) {
+ p = this.db.getRecPtr(p);
+ int partLen= Math.min(length - start, numCharsn);
+ chunk= this.db.getChunk(p);
+ if (useBytes) {
+ chunk.getCharsFromBytes(p + CHARSN, chars, start, partLen);
+ } else {
+ chunk.getChars(p + CHARSN, chars, start, partLen);
+ }
+ start += partLen;
+ p= p + NEXTN;
+ }
+ return chars;
+ }
+
+ @Override
+ public void delete() throws IndexException {
+ int length = this.db.getInt(this.record + LENGTH);
+ final boolean useBytes = length < 0;
+ int numChars1 = NUM_CHARS1;
+ int numCharsn = NUM_CHARSN;
+ if (useBytes) {
+ length= -length;
+ numChars1 *= 2;
+ numCharsn *= 2;
+ }
+ long nextRecord = this.db.getRecPtr(this.record + NEXT1);
+ this.db.free(this.record, Database.POOL_STRING_LONG);
+ length -= numChars1;
+
+ // Middle records.
+ while (length > numCharsn) {
+ length -= numCharsn;
+ long nextnext = this.db.getRecPtr(nextRecord + NEXTN);
+ this.db.free(nextRecord, Database.POOL_STRING_LONG);
+ nextRecord = nextnext;
+ }
+
+ // Last record.
+ this.db.free(nextRecord, Database.POOL_STRING_LONG);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ try {
+ if (obj instanceof LongString) {
+ LongString lstr = (LongString)obj;
+ if (this.db == lstr.db && this.record == lstr.record)
+ return true;
+ return compare(lstr, true) == 0;
+ }
+ if (obj instanceof char[]) {
+ return compare((char[]) obj, true) == 0;
+ }
+ if (obj instanceof String) {
+ return compare((String) obj, true) == 0;
+ }
+ } catch (IndexException e) {
+ Package.log(e);
+ }
+ return false;
+ }
+
+ /**
+ * Compatible with {@link String#hashCode()}
+ */
+ @Override
+ public int hashCode() {
+ int h = this.hash;
+ if (h == 0) {
+ char chars[];
+ chars = getChars();
+ final int len = chars.length;
+ for (int i = 0; i < len; i++) {
+ h = 31 * h + chars[i];
+ }
+ this.hash = h;
+ }
+ return h;
+ }
+
+ @Override
+ public int compare(IString string, boolean caseSensitive) throws IndexException {
+ return ShortString.compare(getChars(), string.getChars(), caseSensitive);
+ }
+
+ @Override
+ public int compare(String other, boolean caseSensitive) throws IndexException {
+ return ShortString.compare(getChars(), other.toCharArray(), caseSensitive);
+ }
+
+ @Override
+ public int compare(char[] other, boolean caseSensitive) throws IndexException {
+ return ShortString.compare(getChars(), other, caseSensitive);
+ }
+
+ @Override
+ public int compareCompatibleWithIgnoreCase(IString string) throws IndexException {
+ return ShortString.compareCompatibleWithIgnoreCase(getChars(), string.getChars());
+ }
+
+ @Override
+ public int comparePrefix(char[] other, boolean caseSensitive) throws IndexException {
+ return ShortString.comparePrefix(getChars(), other, caseSensitive);
+ }
+
+ @Override
+ public String getString() throws IndexException {
+ return new String(getChars());
+ }
+
+ @Override
+ public int compareCompatibleWithIgnoreCase(char[] other) throws IndexException {
+ return ShortString.compareCompatibleWithIgnoreCase(getChars(), other);
+ }
+
+ @Override
+ public int length() {
+ return this.db.getInt(this.record + LENGTH);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/MemoryStats.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/MemoryStats.java
new file mode 100644
index 000000000..8de5777df
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/MemoryStats.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+
+public class MemoryStats {
+ public static final int TOTAL_MALLOC_POOLS = 64;
+ /** The size of the statistics for a single malloc pool */
+ public static final int SIZE = TOTAL_MALLOC_POOLS * PoolStats.RECORD_SIZE;
+
+ private Map<Integer, PoolStats> stats = new HashMap<>();
+
+ public final long address;
+ private Chunk db;
+
+ public static final class PoolStats {
+ public static int POOL_ID_OFFSET = 0;
+ public static int NUM_ALLOCATIONS_OFFSET = POOL_ID_OFFSET + Database.SHORT_SIZE;
+ public static int TOTAL_SIZE_OFFSET = NUM_ALLOCATIONS_OFFSET + Database.LONG_SIZE;
+
+ public static final int RECORD_SIZE = TOTAL_SIZE_OFFSET + Database.LONG_SIZE;
+
+ short poolId;
+ long numAllocations;
+ long totalSize;
+ long address;
+
+ public PoolStats(Chunk db, long address) {
+ this.address = address;
+ this.poolId = db.getShort(POOL_ID_OFFSET + address);
+ this.numAllocations = db.getLong(NUM_ALLOCATIONS_OFFSET + address);
+ this.totalSize = db.getLong(TOTAL_SIZE_OFFSET + address);
+ }
+
+ public void setAllocations(Chunk db, long numAllocations) {
+ this.numAllocations = numAllocations;
+ db.putLong(this.address + NUM_ALLOCATIONS_OFFSET, numAllocations);
+ }
+
+ public void setTotalSize(Chunk db, long totalSize) {
+ this.totalSize = totalSize;
+ db.putLong(this.address + TOTAL_SIZE_OFFSET, totalSize);
+ }
+
+ public void setPoolId(Chunk db, short poolId) {
+ this.poolId = poolId;
+ db.putShort(this.address + POOL_ID_OFFSET, poolId);
+ }
+
+ public long getNumAllocations() {
+ return this.numAllocations;
+ }
+
+ public short getPoolId() {
+ return this.poolId;
+ }
+
+ public long getTotalSize() {
+ return this.totalSize;
+ }
+ }
+
+ public MemoryStats(Chunk db, long address) {
+ this.db = db;
+ this.address = address;
+ }
+
+ public void printMemoryStats(NdNodeTypeRegistry<?> nodeRegistry) {
+ StringBuilder builder = new StringBuilder();
+ for (PoolStats next : getSortedPools()) {
+ builder.append(getPoolName(nodeRegistry, next.poolId));
+ builder.append(" "); //$NON-NLS-1$
+ builder.append(next.numAllocations);
+ builder.append(" allocations, "); //$NON-NLS-1$
+ builder.append(next.totalSize);
+ builder.append(" bytes\n"); //$NON-NLS-1$
+ }
+ System.out.println(builder.toString());
+ }
+
+ private String getPoolName(NdNodeTypeRegistry<?> registry, int poolId) {
+ switch (poolId) {
+ case Database.POOL_MISC: return "Miscellaneous"; //$NON-NLS-1$
+ case Database.POOL_BTREE: return "B-Trees"; //$NON-NLS-1$
+ case Database.POOL_DB_PROPERTIES: return "DB Properties"; //$NON-NLS-1$
+ case Database.POOL_STRING_LONG: return "Long Strings"; //$NON-NLS-1$
+ case Database.POOL_STRING_SHORT: return "Short Strings"; //$NON-NLS-1$
+ case Database.POOL_LINKED_LIST: return "Linked Lists"; //$NON-NLS-1$
+ case Database.POOL_STRING_SET: return "String Sets"; //$NON-NLS-1$
+ case Database.POOL_GROWABLE_ARRAY: return "Growable Arrays"; //$NON-NLS-1$
+ default:
+ if (poolId >= Database.POOL_FIRST_NODE_TYPE) {
+ ITypeFactory<?> type = registry.getClassForType((short)(poolId - Database.POOL_FIRST_NODE_TYPE));
+
+ if (type != null) {
+ return type.getElementClass().getSimpleName();
+ }
+ }
+ return "Unknown memory pool " + poolId; //$NON-NLS-1$
+ }
+ }
+
+ public Collection<PoolStats> getPools() {
+ return this.stats.values();
+ }
+
+ public List<PoolStats> getSortedPools() {
+ List<PoolStats> unsorted = new ArrayList<>();
+ unsorted.addAll(getPools());
+ Collections.sort(unsorted, new Comparator<PoolStats>() {
+ @Override
+ public int compare(PoolStats o1, PoolStats o2) {
+ return Long.signum(o2.totalSize - o1.totalSize);
+ }
+ });
+ return unsorted;
+ }
+
+ public void recordMalloc(short poolId, long size) {
+ PoolStats toRecord = getPoolStats(poolId);
+ toRecord.setAllocations(this.db, toRecord.numAllocations + 1);
+ toRecord.setTotalSize(this.db, toRecord.totalSize + size);
+ }
+
+ private PoolStats getPoolStats(short poolId) {
+ if (this.stats.isEmpty()) {
+ refresh();
+ }
+ PoolStats result = this.stats.get((int)poolId);
+ if (result == null) {
+ if (this.stats.size() >= TOTAL_MALLOC_POOLS) {
+ throw new IndexException("Too many malloc pools. Please increase the size of TOTAL_MALLOC_POOLS."); //$NON-NLS-1$
+ }
+ // Find the insertion position
+ int idx = 0;
+ for (;;idx++) {
+ PoolStats nextPool = readPool(idx);
+ if (idx > 0 && nextPool.poolId == 0) {
+ break;
+ }
+ if (nextPool.poolId == poolId) {
+ throw new IllegalStateException("The stats were out of sync with the database."); //$NON-NLS-1$
+ }
+ if (nextPool.poolId > poolId) {
+ break;
+ }
+ }
+
+ // Find the last pool position
+ int lastIdx = idx;
+ for (;;lastIdx++) {
+ PoolStats nextPool = readPool(lastIdx);
+ if (lastIdx > 0 && nextPool.poolId == 0) {
+ break;
+ }
+ }
+
+ // Shift all the pools to make room
+ for (int shiftIdx = lastIdx; shiftIdx > idx; shiftIdx--) {
+ PoolStats writeTo = readPool(shiftIdx);
+ PoolStats readFrom = readPool(shiftIdx - 1);
+
+ writeTo.setAllocations(this.db, readFrom.numAllocations);
+ writeTo.setTotalSize(this.db, readFrom.totalSize);
+ writeTo.setPoolId(this.db, readFrom.poolId);
+ }
+
+ result = readPool(idx);
+ result.setAllocations(this.db, 0);
+ result.setTotalSize(this.db, 0);
+ result.setPoolId(this.db, poolId);
+
+ refresh();
+
+ result = this.stats.get((int)poolId);
+ }
+ return result;
+ }
+
+ private List<PoolStats> loadStats() {
+ List<PoolStats> result = new ArrayList<>();
+ for (int idx = 0; idx < TOTAL_MALLOC_POOLS; idx++) {
+ PoolStats next = readPool(idx);
+
+ if (idx > 0 && next.poolId == 0) {
+ break;
+ }
+
+ result.add(next);
+ }
+ return result;
+ }
+
+ public void refresh() {
+ this.stats.clear();
+
+ for (PoolStats next : loadStats()) {
+ this.stats.put((int)next.poolId, next);
+ }
+ }
+
+ public PoolStats readPool(int idx) {
+ return new PoolStats(this.db, this.address + idx * PoolStats.RECORD_SIZE);
+ }
+
+ public void recordFree(short poolId, long size) {
+ PoolStats toRecord = getPoolStats(poolId);
+ if (toRecord.numAllocations <= 0 || toRecord.totalSize < size) {
+ throw new IndexException("Attempted to free more memory from pool " + poolId + " than was ever allocated"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ toRecord.setAllocations(this.db, toRecord.numAllocations - 1);
+ toRecord.setTotalSize(this.db, toRecord.totalSize - size);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/NdStringSet.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/NdStringSet.java
new file mode 100644
index 000000000..37bb613dd
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/NdStringSet.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2013, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andrew Eidsness - Initial implementation
+ */
+
+package org.eclipse.jdt.internal.core.nd.db;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * A container for storing a set of strings in the Database. The container allows only one instance of each
+ * string to be stored.
+ * <p>
+ * This implementation should only be used when the set is expected to be small. It uses a singly linked list
+ * for storing strings in Database. Which means that a linear lookup is needed to find strings in the list. An
+ * in-memory, lazily-loaded, cache is provided so the list will only be fully retrieved once in the lifetime
+ * of this instance. A BTree will be more efficient for larger sets.
+ */
+public class NdStringSet {
+ private final Database db;
+
+ private long ptr;
+ private long head;
+ private long loaded;
+
+ // A lazily initialized, in-memory cache that maps a persisted string to its storage record.
+ private Map<String, Long> lazyCache;
+
+ public NdStringSet(Database db, long ptr) throws CoreException {
+ this.db = db;
+ this.ptr = ptr;
+
+ this.head = 0;
+ this.loaded = 0;
+ }
+
+ public void clearCaches() {
+ this.head = 0;
+ this.loaded = 0;
+
+ if (this.lazyCache != null)
+ this.lazyCache = null;
+ }
+
+ private long getHead() throws CoreException {
+ if (this.head == 0)
+ this.head = this.db.getRecPtr(this.ptr);
+ return this.head;
+ }
+
+ // A simple enum describing the type of the information that is stored in the Database. Each
+ // enumerator represents a single field in the persistent structure and is able to answer its
+ // offset in that structure.
+ private static enum NodeType {
+ Next, Item, _last;
+
+ // NOTE: All fields are pointers, if that changes then these initializations will need
+ // to be updated.
+ public final long offset = ordinal() * Database.PTR_SIZE;
+ public static final int sizeof = (int) _last.offset;
+
+ /** Return the value of the pointer stored in this field in the given instance. */
+ public long get(Database db, long instance) throws CoreException {
+ return db.getRecPtr(instance + this.offset);
+ }
+
+ /** Store the given pointer into this field in the given instance. */
+ public void put(Database db, long instance, long value) throws CoreException {
+ db.putRecPtr(instance + this.offset, value);
+ }
+ }
+
+ /**
+ * Adds the given string to the receiving set. May cause the entire list to be loaded from the Database
+ * while testing for uniqueness. Returns the record of the string that was inserted into the list.
+ */
+ public long add(String str) throws CoreException {
+ long record = find(str);
+ if (record != 0)
+ return record;
+
+ IString string = this.db.newString(str);
+ record = string.getRecord();
+
+ long new_node = this.db.malloc(NodeType.sizeof, Database.POOL_STRING_SET);
+ NodeType.Next.put(this.db, new_node, getHead());
+ NodeType.Item.put(this.db, new_node, record);
+
+ if (this.lazyCache == null)
+ this.lazyCache = new HashMap<String, Long>();
+ this.lazyCache.put(str, record);
+
+ // If the Database has already been partially searched, then the loaded pointer will be after the
+ // head. Since we've already put this new record into the lazy cache, there is no reason to try to
+ // load it again. We put the new node at the start of the list so that it will be before the loaded
+ // pointer.
+ this.head = new_node;
+ if (this.loaded == 0)
+ this.loaded = new_node;
+ this.db.putRecPtr(this.ptr, new_node);
+ return record;
+ }
+
+ /**
+ * Search for the given string in the receiver. This could cause the entire list to be loaded from the
+ * Database. The results are cached, so the list will only be loaded one time during the lifetime of this
+ * instance. Returns the record of the String.
+ */
+ public long find(String str) throws CoreException {
+ if (this.lazyCache != null) {
+ Long l = this.lazyCache.get(str);
+ if (l != null)
+ return l.longValue();
+ }
+
+ // if there is nothing in the Database, then there is nothing to load
+ if (getHead() == 0)
+ return 0;
+
+ // otherwise prepare the cache for the data that is about to be loaded
+ if (this.lazyCache == null)
+ this.lazyCache = new HashMap<String, Long>();
+
+ // if nothing has been loaded, then start loading with the head node, otherwise continue
+ // loading from whatever is after the last loaded node
+ long curr = this.loaded == 0 ? getHead() : NodeType.Next.get(this.db, this.loaded);
+ while (curr != 0) {
+ long next = NodeType.Next.get(this.db, curr);
+ long item = NodeType.Item.get(this.db, curr);
+
+ IString string = this.db.getString(item);
+
+ // put the value into the cache
+ this.lazyCache.put(string.getString(), Long.valueOf(item));
+
+ // return immediately if this is the target
+ if (string.compare(str, true) == 0)
+ return item;
+
+ // otherwise keep looking
+ this.loaded = curr;
+ curr = next;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Return a pointer to the record of the String that was removed.
+ */
+ public long remove(String str) throws CoreException {
+ if (this.lazyCache != null)
+ this.lazyCache.remove(str);
+
+ long prev = 0;
+ long curr = getHead();
+ while (curr != 0) {
+ long next = NodeType.Next.get(this.db, curr);
+ long item = NodeType.Item.get(this.db, curr);
+
+ IString string = this.db.getString(item);
+
+ if (string.compare(str, true) == 0) {
+ if (this.head != curr)
+ NodeType.Next.put(this.db, prev, next);
+ else {
+ this.db.putRecPtr(this.ptr, next);
+ this.head = next;
+ }
+
+ this.db.free(curr, Database.POOL_STRING_SET);
+ return item;
+ }
+
+ prev = curr;
+ curr = next;
+ }
+
+ return 0;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Package.java
new file mode 100644
index 000000000..b68df3cb2
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/Package.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/**
+ * This class is not intended to be referenced by clients
+ */
+/* package */ class Package {
+ public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+ /**
+ * Status code for core exception that is thrown if a database grew larger than the supported limit.
+ */
+ public static final int STATUS_DATABASE_TOO_LARGE = 4;
+
+ public static void log(Throwable e) {
+ String msg= e.getMessage();
+ if (msg == null) {
+ log("Error", e); //$NON-NLS-1$
+ } else {
+ log("Error: " + msg, e); //$NON-NLS-1$
+ }
+ }
+
+ public static void log(String message, Throwable e) {
+ log(createStatus(message, e));
+ }
+
+ public static IStatus createStatus(String msg, Throwable e) {
+ return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+ }
+
+ public static void log(IStatus status) {
+ JavaCore.getPlugin().getLog().log(status);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ShortString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ShortString.java
new file mode 100644
index 000000000..09992a564
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/db/ShortString.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2016 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * QNX - Initial API and implementation
+ * Andrew Ferguson (Symbian)
+ * Markus Schorn (Wind River Systems)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.db;
+
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * This is for strings that fit inside a single chunk.
+ */
+public class ShortString implements IString {
+ private final Database db;
+ private final long record;
+ private int hash;
+
+ private static final int LENGTH = 0;
+ private static final int CHARS = 4;
+
+ public static final int MAX_BYTE_LENGTH = Database.MAX_MALLOC_SIZE - CHARS;
+
+ public ShortString(Database db, long offset) {
+ this.db = db;
+ this.record = offset;
+ }
+
+ public ShortString(Database db, char[] chars, boolean useBytes) throws IndexException {
+ final int n = chars.length;
+ this.db = db;
+
+ this.record = db.malloc(CHARS + (useBytes ? n : 2 * n), Database.POOL_STRING_SHORT);
+ Chunk chunk = db.getChunk(this.record);
+ chunk.putInt(this.record + LENGTH, useBytes ? -n : n);
+ long p = this.record + CHARS;
+ if (useBytes) {
+ chunk.putCharsAsBytes(p, chars, 0, n);
+ } else {
+ chunk.putChars(p, chars, 0, n);
+ }
+ }
+
+ @Override
+ public long getRecord() {
+ return this.record;
+ }
+
+ @Override
+ public void delete() throws IndexException {
+ this.db.free(this.record, Database.POOL_STRING_SHORT);
+ }
+
+ @Override
+ public char[] getChars() throws IndexException {
+ final Chunk chunk = this.db.getChunk(this.record);
+ final int l = chunk.getInt(this.record + LENGTH);
+ final int length = Math.abs(l);
+ final char[] chars = new char[length];
+ if (l < 0) {
+ chunk.getCharsFromBytes(this.record + CHARS, chars, 0, length);
+ } else {
+ chunk.getChars(this.record + CHARS, chars, 0, length);
+ }
+ return chars;
+ }
+
+ @Override
+ public String getString() throws IndexException {
+ return new String(getChars());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ try {
+ if (obj instanceof ShortString) {
+ ShortString string = (ShortString)obj;
+ if (this.db == string.db && this.record == string.record)
+ return true;
+
+ Chunk chunk1 = this.db.getChunk(this.record);
+ Chunk chunk2 = string.db.getChunk(string.record);
+
+ int n1 = chunk1.getInt(this.record);
+ int n2 = chunk2.getInt(string.record);
+ if (n1 != n2)
+ return false;
+
+ return CharArrayUtils.equals(getChars(), string.getChars());
+ }
+ if (obj instanceof char[]) {
+ char[] chars = (char[])obj;
+
+ // Make sure size is the same
+ if (length() != chars.length)
+ return false;
+
+ return CharArrayUtils.equals(getChars(), chars);
+ } else if (obj instanceof String) {
+ String string = (String)obj;
+ if (length() != string.length())
+ return false;
+
+ return CharArrayUtils.equals(getChars(), string.toCharArray());
+ }
+ } catch (IndexException e) {
+ Package.log(e);
+ }
+ return false;
+ }
+
+ /**
+ * Compatible with {@link String#hashCode()}
+ */
+ @Override
+ public int hashCode() {
+ int h = this.hash;
+ if (h == 0) {
+ char chars[];
+ chars = getChars();
+ final int len = chars.length;
+ for (int i = 0; i < len; i++) {
+ h = 31 * h + chars[i];
+ }
+ this.hash = h;
+ }
+ return h;
+ }
+
+ public static int compare(final char[] chars, char[] other, boolean caseSensitive) {
+ final int n = Math.min(chars.length, other.length);
+ for (int i = 0; i < n; i++) {
+ int cmp= compareChars(chars[i], other[i], caseSensitive);
+ if (cmp != 0)
+ return cmp;
+ }
+ return chars.length - other.length;
+ }
+
+ @Override
+ public int compare(char[] other, boolean caseSensitive) throws IndexException {
+ return compare(getChars(), other, caseSensitive);
+ }
+
+ @Override
+ public int compare(IString string, boolean caseSensitive) throws IndexException {
+ return compare(getChars(), string.getChars(), caseSensitive);
+ }
+
+ @Override
+ public int compare(String other, boolean caseSensitive) throws IndexException {
+ return compare(getChars(), other.toCharArray(), caseSensitive);
+ }
+
+ @Override
+ public int compareCompatibleWithIgnoreCase(IString string) throws IndexException {
+ return compareCompatibleWithIgnoreCase(string.getChars());
+ }
+
+ @Override
+ public int compareCompatibleWithIgnoreCase(char[] other) throws IndexException {
+ return compareCompatibleWithIgnoreCase(getChars(), other);
+ }
+
+ public static int compareCompatibleWithIgnoreCase(final char[] chars, char[] other) {
+ final int n = Math.min(chars.length, other.length);
+ int sensitiveCmp= 0;
+
+ for (int i = 0; i < n; i++) {
+ final char c1= chars[i];
+ final char c2= other[i];
+ if (c1 != c2) {
+ int cmp= compareChars(c1, c2, false); // insensitive
+ if (cmp != 0)
+ return cmp;
+
+ if (sensitiveCmp == 0) {
+ if (c1 < c2) {
+ sensitiveCmp= -1;
+ } else {
+ sensitiveCmp= 1;
+ }
+ }
+ }
+ }
+ int cmp= chars.length - other.length;
+ if (cmp != 0)
+ return cmp;
+
+ return sensitiveCmp;
+ }
+
+ @Override
+ public int comparePrefix(char[] other, boolean caseSensitive) throws IndexException {
+ return comparePrefix(getChars(), other, caseSensitive);
+ }
+
+ public static int comparePrefix(final char[] chars, char[] other, boolean caseSensitive) {
+ final int n = Math.min(chars.length, other.length);
+
+ for (int i = 0; i < n; i++) {
+ int cmp= compareChars(chars[i], other[i], caseSensitive);
+ if (cmp != 0)
+ return cmp;
+ }
+ if (chars.length < other.length)
+ return -1;
+
+ return 0;
+ }
+
+ /**
+ * Compare characters case-sensitively, or case-insensitively.
+ *
+ * <b>Limitation</b> This only maps the range a-z,A-Z onto each other
+ * @param a a character
+ * @param b a character
+ * @param caseSensitive whether to compare case-sensitively
+ * @return
+ * <ul>
+ * <li>-1 if a < b
+ * <li>0 if a == b
+ * <li>1 if a > b
+ * </ul>
+ */
+ public static int compareChars(char a, char b, boolean caseSensitive) {
+ if (caseSensitive) {
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ } else {
+ if (a != b) {
+ a= a >= 'a' && a <='z' ? (char) (a - 32) : a;
+ b= b >= 'a' && b <='z' ? (char) (b - 32) : b;
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+/* TODO - this is more correct than the above implementation, but we need to
+ * benchmark first.
+ *
+ * public static int compareChars(char a, char b, boolean caseSensitive) {
+ if (caseSensitive) {
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ } else {
+ if (a != b) {
+ a = Character.toUpperCase(a);
+ b = Character.toUpperCase(b);
+ if (a != b) {
+ a = Character.toLowerCase(a);
+ b = Character.toLowerCase(b);
+ if (a != b) {
+ if (a < b)
+ return -1;
+ if (a > b)
+ return 1;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+*/
+
+ @Override
+ public String toString() {
+ try {
+ return getString();
+ } catch (IndexException e) {
+ return super.toString();
+ }
+ }
+
+ @Override
+ public int length() {
+ return Math.abs(this.db.getInt(this.record + LENGTH));
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java
new file mode 100644
index 000000000..bab45d4dd
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/Field.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Used to represent a single field of an object stored in the database. Objects
+ * which store themselves in the database should store a set of static final
+ * FieldDefinitions at the top of their class definition to indicate their memory map.
+ * This serves as a standard way to document the memory map for such structs, provides
+ * access to the field offsets, and provides a convenience getter.
+ * <p>
+ * There are two ways to use this. Callers can either use the "get" method to access
+ * the value of the field, or can use the public "offset" attribute to perform the reads
+ * manually. The get function is more convenient but allocates objects and so should
+ * probably not be used for frequently-accessed fields or primitive types that would
+ * end up being autoboxed unnecessarily.
+ *
+ * @param <T>
+ */
+public final class Field<T> implements IField, IDestructableField {
+ private int offset;
+ public final ITypeFactory<T> factory;
+
+ public Field(ITypeFactory<T> objectFactory) {
+ this.factory = objectFactory;
+ }
+
+ public T get(Nd nd, long address) {
+ return this.factory.create(nd, address + this.offset);
+ }
+
+ public boolean hasDestructor() {
+ return this.factory.hasDestructor();
+ }
+
+ @Override
+ public void destruct(Nd nd, long address) {
+ this.factory.destruct(nd, address + this.offset);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return this.factory.getRecordSize();
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldByte.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldByte.java
new file mode 100644
index 000000000..c7a9ef5e0
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldByte.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type byte. Can be used in place of {@link Field}&lt{@link Byte}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldByte implements IField {
+ private int offset;
+
+ public FieldByte() {
+ }
+
+ public byte get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getByte(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, byte newValue) {
+ nd.getDB().putByte(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.BYTE_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldChar.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldChar.java
new file mode 100644
index 000000000..e4b0e178a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldChar.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type char. Can be used in place of {@link Field}&lt{@link Character}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldChar implements IField {
+ private int offset;
+
+ public FieldChar() {
+ }
+
+ public char get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getChar(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, char newValue) {
+ nd.getDB().putChar(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.CHAR_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldDouble.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldDouble.java
new file mode 100644
index 000000000..f0932e209
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldDouble.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type double. Can be used in place of {@link Field}&lt{@link Double}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldDouble implements IField {
+ private int offset;
+
+ public FieldDouble() {
+ }
+
+ public double get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getDouble(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, double newValue) {
+ nd.getDB().putDouble(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.DOUBLE_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldFloat.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldFloat.java
new file mode 100644
index 000000000..4ddd09311
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldFloat.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type float. Can be used in place of {@link Field}&lt{@link Float}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldFloat implements IField {
+ private int offset;
+
+ public FieldFloat() {
+ }
+
+ public float get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getFloat(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, float newValue) {
+ nd.getDB().putFloat(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.FLOAT_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldInt.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldInt.java
new file mode 100644
index 000000000..06e9b8a71
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldInt.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type int. Can be used in place of {@link Field}&lt{@link Integer}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldInt implements IField {
+ private int offset;
+
+ public FieldInt() {
+ }
+
+ public int get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getInt(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, int newValue) {
+ nd.getDB().putInt(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.INT_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldLong.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldLong.java
new file mode 100644
index 000000000..6a66ac26f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldLong.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type long. Can be used in place of {@link Field}&lt{@link Long}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldLong implements IField {
+ private int offset;
+
+ public FieldLong() {
+ }
+
+ public long get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getLong(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, long newValue) {
+ nd.getDB().putLong(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.LONG_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java
new file mode 100644
index 000000000..ed644953d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldManyToOne.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+
+/**
+ * Holds the n side of a n..1 relationship. Declares a Nd field which is a pointer of a NdNode of the specified
+ * type. {@link FieldManyToOne} forms a one-to-many relationship with {@link FieldOneToMany}. Whenever a
+ * {@link FieldManyToOne} points to an object, the inverse pointer is automatically inserted into the matching back
+ * pointer list.
+ */
+public class FieldManyToOne<T extends NdNode> implements IDestructableField, IField, IRefCountedField {
+ public final static FieldPointer TARGET;
+ public final static FieldInt BACKPOINTER_INDEX;
+
+ private int offset;
+ Class<T> targetType;
+ final Class<? extends NdNode> localType;
+ FieldOneToMany<?> backPointer;
+ @SuppressWarnings("rawtypes")
+ private final static StructDef<FieldManyToOne> type;
+ /**
+ * True iff the other end of this pointer should delete this object when its end of the pointer is cleared.
+ */
+ public final boolean pointsToOwner;
+
+ static {
+ type = StructDef.createAbstract(FieldManyToOne.class);
+ TARGET = type.addPointer();
+ BACKPOINTER_INDEX = type.addInt();
+ type.done();
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private FieldManyToOne(Class<? extends NdNode> localType, FieldOneToMany<?> backPointer, boolean pointsToOwner) {
+ this.localType = localType;
+ this.pointsToOwner = pointsToOwner;
+
+ if (backPointer != null) {
+ if (backPointer.forwardPointer != null && backPointer.forwardPointer != this) {
+ throw new IllegalArgumentException(
+ "Attempted to construct a FieldNodePointer referring to a backpointer list that is already in use" //$NON-NLS-1$
+ + " by another field"); //$NON-NLS-1$
+ }
+ backPointer.targetType = (Class) localType;
+ this.targetType = (Class) backPointer.localType;
+ backPointer.forwardPointer = this;
+ }
+ this.backPointer = backPointer;
+ }
+
+ public static <T extends NdNode, B extends NdNode> FieldManyToOne<T> create(StructDef<B> builder,
+ FieldOneToMany<B> forwardPointer) {
+ FieldManyToOne<T> result = new FieldManyToOne<T>(builder.getStructClass(), forwardPointer, false);
+ builder.add(result);
+ builder.addDestructableField(result);
+ return result;
+ }
+
+ /**
+ * Creates a many-to-one pointer which points to this object's owner. If the pointer is non-null when the owner is
+ * deleted, this object will be deleted too.
+ *
+ * @param builder the struct to which the field will be added
+ * @param forwardPointer the field which holds the pointer in the other direction
+ * @return a newly constructed field
+ */
+ public static <T extends NdNode, B extends NdNode> FieldManyToOne<T> createOwner(StructDef<B> builder,
+ FieldOneToMany<B> forwardPointer) {
+
+ FieldManyToOne<T> result = new FieldManyToOne<T>(builder.getStructClass(), forwardPointer, true);
+ builder.add(result);
+ builder.addDestructableField(result);
+ builder.addOwnerField(result);
+ return result;
+ }
+
+ public T get(Nd nd, long address) {
+ return NdNode.load(nd, getAddress(nd, address), this.targetType);
+ }
+
+ public long getAddress(Nd nd, long address) {
+ return nd.getDB().getRecPtr(address + this.offset);
+ }
+
+ /**
+ * Directs this pointer to the given target. Also removes this pointer from the old backpointer list (if any) and
+ * inserts it into the new backpointer list (if any)
+ */
+ public void put(Nd nd, long address, T value) {
+ if (value != null) {
+ put(nd, address, value.address);
+ } else {
+ put(nd, address, 0);
+ }
+ }
+
+ public void put(Nd nd, long address, long newTargetAddress) {
+ long fieldStart = address + this.offset;
+ if (this.backPointer == null) {
+ throw new IllegalStateException("FieldNodePointer must be associated with a FieldBackPointer"); //$NON-NLS-1$
+ }
+
+ long oldTargetAddress = TARGET.get(nd, fieldStart);
+ if (oldTargetAddress == newTargetAddress) {
+ return;
+ }
+
+ detachFromOldTarget(nd, address, oldTargetAddress);
+
+ TARGET.put(nd, fieldStart, newTargetAddress);
+ if (newTargetAddress != 0) {
+ // Note that newValue is the address of the backpointer list and record (the address of the struct
+ // containing the forward pointer) is the value being inserted into the list.
+ BACKPOINTER_INDEX.put(nd, fieldStart, this.backPointer.add(nd, newTargetAddress, address));
+ } else {
+ if (this.pointsToOwner) {
+ nd.scheduleDeletion(address);
+ }
+ }
+ }
+
+ protected void detachFromOldTarget(Nd nd, long address, long oldTargetAddress) {
+ long fieldStart = address + this.offset;
+ if (oldTargetAddress != 0) {
+ int oldIndex = BACKPOINTER_INDEX.get(nd, fieldStart);
+
+ this.backPointer.remove(nd, oldTargetAddress, oldIndex);
+
+ short targetTypeId = NdNode.NODE_TYPE.get(nd, oldTargetAddress);
+
+ ITypeFactory<T> typeFactory = nd.getTypeFactory(targetTypeId);
+
+ if (typeFactory.getDeletionSemantics() == StructDef.DeletionSemantics.REFCOUNTED
+ && typeFactory.isReadyForDeletion(nd, oldTargetAddress)) {
+ nd.scheduleDeletion(oldTargetAddress);
+ }
+ }
+ }
+
+ /**
+ * Called when the index of this forward pointer has moved in the backpointer list. Adjusts the index.
+ * <p>
+ * Not intended to be called by clients. This is invoked by {@link FieldOneToMany} whenever it reorders elements in
+ * the array.
+ */
+ void adjustIndex(Nd nd, long address, int index) {
+ BACKPOINTER_INDEX.put(nd, address + this.offset, index);
+ }
+
+ @Override
+ public void destruct(Nd nd, long address) {
+ long fieldStart = address + this.offset;
+ long oldTargetAddress = TARGET.get(nd, fieldStart);
+ detachFromOldTarget(nd, address, oldTargetAddress);
+ TARGET.put(nd, fieldStart, 0);
+ }
+
+ void clearedByBackPointer(Nd nd, long address) {
+ long fieldStart = this.offset + address;
+ FieldManyToOne.TARGET.put(nd, fieldStart, 0);
+ FieldManyToOne.BACKPOINTER_INDEX.put(nd, fieldStart, 0);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return type.size();
+ }
+
+ @Override
+ public boolean hasReferences(Nd nd, long address) {
+ long fieldStart = this.offset + address;
+ long target = TARGET.get(nd, fieldStart);
+ return target != 0;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java
new file mode 100644
index 000000000..8f95c681f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToMany.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.RawGrowableArray;
+
+/**
+ * Holds the 1 side of a 1..n relationship between two objects. FieldNodePointer and FieldBackPointer fields always go
+ * together in pairs.
+ */
+public class FieldOneToMany<T extends NdNode> implements IDestructableField, IRefCountedField, IField {
+ private int offset;
+ public Class<T> targetType;
+ public final Class<? extends NdNode> localType;
+ private final RawGrowableArray backPointerArray;
+ FieldManyToOne<?> forwardPointer;
+
+ public interface Visitor<T> {
+ public void visit(int index, T toVisit);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private FieldOneToMany(Class<? extends NdNode> localType, FieldManyToOne<? extends NdNode> forwardPointer,
+ int inlineElements) {
+ this.localType = localType;
+
+ if (forwardPointer != null) {
+ if (forwardPointer.backPointer != null && forwardPointer.backPointer != this) {
+ throw new IllegalArgumentException(
+ "Attempted to construct a FieldBackPointer referring to a forward pointer that is already in use" //$NON-NLS-1$
+ + " by another field"); //$NON-NLS-1$
+ }
+ forwardPointer.targetType = (Class)localType;
+ this.targetType = (Class)forwardPointer.localType;
+ forwardPointer.backPointer = this;
+ }
+ this.forwardPointer = forwardPointer;
+ this.backPointerArray = new RawGrowableArray(inlineElements);
+ }
+
+ /**
+ * Creates a {@link FieldOneToMany} using the given builder. It will hold the many side of a one-to-many
+ * relationship with nodeType.
+ *
+ * @param builder builder that is being used to construct the struct containing this field
+ * @param forwardPointer field of the model object which holds the one side of this one-to-many relationship
+ * @param inlineElementCount number of inline elements. If this is nonzero, space for this number elements is
+ * preallocated and reserved in the header. The first few elements inserted will be stored here. For relationships
+ * which will usually have more than a certain number of participants, using a small number of inline elements will
+ * offer a performance improvement. For relationships that will normally be empty, this should be 0.
+ * @return the newly constructed backpointer field
+ */
+ public static <T extends NdNode, B extends NdNode> FieldOneToMany<T> create(StructDef<B> builder,
+ FieldManyToOne<B> forwardPointer, int inlineElementCount) {
+ FieldOneToMany<T> result = new FieldOneToMany<T>(builder.getStructClass(), forwardPointer,
+ inlineElementCount);
+ builder.add(result);
+ builder.addDestructableField(result);
+ builder.addRefCountedField(result);
+ return result;
+ }
+
+ public static <T extends NdNode, B extends NdNode> FieldOneToMany<T> create(StructDef<B> builder,
+ FieldManyToOne<B> forwardPointer) {
+ return create(builder, forwardPointer, 0);
+ }
+
+ public void accept(Nd nd, long address, Visitor<T> visitor) {
+ int size = size(nd, address);
+
+ for (int idx = 0; idx < size; idx++) {
+ visitor.visit(idx, get(nd, address, idx));
+ }
+ }
+
+ public List<T> asList(Nd nd, long address) {
+ final List<T> result = new ArrayList<>(size(nd, address));
+
+ accept(nd, address, new Visitor<T>() {
+ @Override
+ public void visit(int index, T toVisit) {
+ result.add(toVisit);
+ }
+ });
+
+ return result;
+ }
+
+ public boolean isEmpty(Nd nd, long address) {
+ return this.backPointerArray.isEmpty(nd, address + this.offset);
+ }
+
+ public int size(Nd nd, long address) {
+ return this.backPointerArray.size(nd, address + this.offset);
+ }
+
+ public T get(Nd nd, long address, int index) {
+ long nextPointer = this.backPointerArray.get(nd, address + this.offset, index);
+
+ return NdNode.load(nd, nextPointer, this.targetType);
+ }
+
+ /**
+ * Removes the given index from the list. If another element is swapped into the removed element's
+ * location, that element's index will be updated. The removed element itself will not be modified. The
+ * caller is responsible for nulling out the pointer and updating its index if necessary.
+ * <p>
+ * Not intended to be called by clients. The normal way to remove something from a backpointer list is
+ * by calling {@link FieldManyToOne#put}, which performs the appropriate removals automatically.
+ */
+ void remove(Nd nd, long address, int index) {
+ long swappedElement = this.backPointerArray.remove(nd, address + this.offset, index);
+
+ if (swappedElement != 0) {
+ this.forwardPointer.adjustIndex(nd, swappedElement, index);
+ }
+ }
+
+ /**
+ * Addss the given forward pointer to the list and returns the insertion index. This should not be invoked
+ * directly by clients. The normal way to insert into a backpointer list is to assign a forward pointer.
+ */
+ int add(Nd nd, long address, long value) {
+ return this.backPointerArray.add(nd, address + this.offset, value);
+ }
+
+ /**
+ * Returns the record size of the back pointer list
+ */
+ public int getRecordSize() {
+ return this.backPointerArray.getRecordSize();
+ }
+
+ public void ensureCapacity(Nd nd, long address, int capacity) {
+ long arrayAddress = address + this.offset;
+ this.backPointerArray.ensureCapacity(nd, arrayAddress, capacity);
+ }
+
+ @Override
+ public void destruct(Nd nd, long address) {
+ long arrayAddress = address + this.offset;
+ int size = size(nd, address);
+
+ boolean isOwner = this.forwardPointer.pointsToOwner;
+ for (int idx = 0; idx < size; idx++) {
+ long target = this.backPointerArray.get(nd, arrayAddress, idx);
+
+ this.forwardPointer.clearedByBackPointer(nd, target);
+
+ if (isOwner) {
+ nd.scheduleDeletion(target);
+ }
+ }
+
+ this.backPointerArray.destruct(nd, arrayAddress);
+ }
+
+ public int getCapacity(Nd nd, long address) {
+ return this.backPointerArray.getCapacity(nd, address + this.offset);
+ }
+
+ @Override
+ public boolean hasReferences(Nd nd, long address) {
+ // If this field owns the objects it points to, don't treat the incoming pointers as ref counts
+ if (this.forwardPointer.pointsToOwner) {
+ return false;
+ }
+ return !isEmpty(nd, address);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java
new file mode 100644
index 000000000..c1dd22883
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldOneToOne.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Represents a 1-to-0..1 relationship in a Nd database.
+ */
+public class FieldOneToOne<T extends NdNode> implements IField, IDestructableField, IRefCountedField {
+ private int offset;
+ public final Class<T> nodeType;
+ FieldOneToOne<?> backPointer;
+ private boolean pointsToOwner;
+
+ /**
+ * @param nodeType
+ * @param backPointer
+ */
+ private FieldOneToOne(Class<T> nodeType, FieldOneToOne<?> backPointer, boolean pointsToOwner) {
+ this.nodeType = nodeType;
+
+ if (backPointer != null) {
+ if (backPointer.backPointer != null && backPointer.backPointer != this) {
+ throw new IllegalArgumentException(
+ "Attempted to construct a FieldOneToOne referring to a backpointer list that is already in use" //$NON-NLS-1$
+ + " by another field"); //$NON-NLS-1$
+ }
+ backPointer.backPointer = this;
+ }
+ this.backPointer = backPointer;
+ this.pointsToOwner = pointsToOwner;
+ }
+
+ public static <T extends NdNode, B extends NdNode> FieldOneToOne<T> create(StructDef<B> builder,
+ Class<T> nodeType, FieldOneToOne<B> forwardPointer) {
+
+ FieldOneToOne<T> result = new FieldOneToOne<T>(nodeType, forwardPointer, false);
+ builder.add(result);
+ builder.addDestructableField(result);
+ return result;
+ }
+
+ public static <T extends NdNode, B extends NdNode> FieldOneToOne<T> createOwner(StructDef<B> builder,
+ Class<T> nodeType, FieldOneToOne<B> forwardPointer) {
+
+ FieldOneToOne<T> result = new FieldOneToOne<T>(nodeType, forwardPointer, true);
+ builder.add(result);
+ builder.addDestructableField(result);
+ builder.addOwnerField(result);
+ return result;
+ }
+
+ public T get(Nd nd, long address) {
+ long ptr = nd.getDB().getRecPtr(address + this.offset);
+ return NdNode.load(nd, ptr, this.nodeType);
+ }
+
+ public void put(Nd nd, long address, T target) {
+ cleanup(nd, address);
+ nd.getDB().putRecPtr(address + this.offset, target == null ? 0 : target.address);
+ if (target == null && this.pointsToOwner) {
+ nd.scheduleDeletion(address);
+ }
+ }
+
+ @Override
+ public void destruct(Nd nd, long address) {
+ cleanup(nd, address);
+ }
+
+ private void cleanup(Nd nd, long address) {
+ Database db = nd.getDB();
+ long ptr = db.getRecPtr(address + this.offset);
+ if (ptr != 0) {
+ db.putRecPtr(ptr + this.backPointer.offset, 0);
+ // If we own our target, delete it
+ if (this.backPointer.pointsToOwner) {
+ nd.scheduleDeletion(ptr);
+ }
+ }
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.PTR_SIZE;
+ }
+
+ @Override
+ public boolean hasReferences(Nd nd, long address) {
+ if (this.pointsToOwner) {
+ long ptr = nd.getDB().getRecPtr(address + this.offset);
+ return ptr != 0;
+ }
+ return false;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldPointer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldPointer.java
new file mode 100644
index 000000000..fef317623
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldPointer.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+public class FieldPointer implements IField {
+ private int offset;
+
+ public FieldPointer() {
+ }
+
+ public long get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getRecPtr(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, long newValue) {
+ nd.getDB().putRecPtr(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.PTR_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchIndex.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchIndex.java
new file mode 100644
index 000000000..f265fcfcd
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchIndex.java
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.BTree;
+import org.eclipse.jdt.internal.core.nd.db.IBTreeComparator;
+import org.eclipse.jdt.internal.core.nd.db.IBTreeVisitor;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+/**
+ * Declares a field representing a case-insensitive search tree over elements which are a subtype of NdNode.
+ */
+public class FieldSearchIndex<T extends NdNode> implements IField, IDestructableField {
+ private int offset;
+ private final ITypeFactory<BTree> btreeFactory;
+ FieldSearchKey<?> searchKey;
+ private static IResultRank anything = new IResultRank() {
+ @Override
+ public long getRank(Nd nd, long address) {
+ return 1;
+ }
+ };
+
+ public static final class SearchCriteria {
+ private boolean matchCase = true;
+ private boolean isPrefix = false;
+ private char[] searchString;
+ private short requiredNodeType = -1;
+ private boolean matchingParentNodeAddress = false;
+
+ private SearchCriteria(char[] searchString) {
+ this.searchString = searchString;
+ }
+
+ public static SearchCriteria create(String searchString) {
+ return create(searchString.toCharArray());
+ }
+
+ public static SearchCriteria create(char[] searchString) {
+ return new SearchCriteria(searchString);
+ }
+
+ public SearchCriteria requireNodeType(short type) {
+ this.requiredNodeType = type;
+ return this;
+ }
+
+ public SearchCriteria allowAnyNodeType() {
+ this.requiredNodeType = -1;
+ return this;
+ }
+
+ public SearchCriteria matchCase(boolean match) {
+ this.matchCase = match;
+ return this;
+ }
+
+ public SearchCriteria prefix(boolean isPrefixSearch) {
+ this.isPrefix = isPrefixSearch;
+ return this;
+ }
+//
+// public SearchCriteria requireParentNode(long parentNameAddress) {
+// this.requiredParentNodeAddress = parentNameAddress;
+// return this;
+// }
+
+ public boolean isMatchingParentNodeAddress() {
+ return this.matchingParentNodeAddress;
+ }
+
+ public boolean isMatchingCase() {
+ return this.matchCase;
+ }
+
+ public boolean isPrefixSearch() {
+ return this.isPrefix;
+ }
+
+ public char[] getSearchString() {
+ return this.searchString;
+ }
+//
+// public long getRequiredParentAddress() {
+// return this.requiredParentNodeAddress;
+// }
+
+ public boolean acceptsNodeType(short nodeType) {
+ return this.requiredNodeType == -1 || this.requiredNodeType == nodeType;
+ }
+
+ public boolean requiresSpecificNodeType() {
+ return this.requiredNodeType != -1;
+ }
+ }
+
+ public static interface IResultRank {
+ public long getRank(Nd nd, long address);
+ }
+
+ private abstract class SearchCriteriaToBtreeVisitorAdapter implements IBTreeVisitor {
+ private final SearchCriteria searchCriteria;
+ private final Nd nd;
+
+ public SearchCriteriaToBtreeVisitorAdapter(SearchCriteria searchCriteria, Nd nd) {
+ this.searchCriteria = searchCriteria;
+ this.nd = nd;
+ }
+
+ @Override
+ public int compare(long address) throws IndexException {
+ IString key = FieldSearchIndex.this.searchKey.get(this.nd, address);
+
+ if (this.searchCriteria.isPrefixSearch()) {
+ return key.comparePrefix(this.searchCriteria.getSearchString(), false);
+ } else {
+ return key.compareCompatibleWithIgnoreCase(this.searchCriteria.getSearchString());
+ }
+ }
+
+ @Override
+ public boolean visit(long address) throws IndexException {
+ if (this.searchCriteria.requiresSpecificNodeType()) {
+ short nodeType = NdNode.NODE_TYPE.get(this.nd, address);
+
+ if (!this.searchCriteria.acceptsNodeType(nodeType)) {
+ return true;
+ }
+ }
+
+ IString key = FieldSearchIndex.this.searchKey.get(this.nd, address);
+
+ if (this.searchCriteria.isMatchingCase()) {
+ if (this.searchCriteria.isPrefixSearch()) {
+ if (key.comparePrefix(this.searchCriteria.getSearchString(), true) != 0) {
+ return true;
+ }
+ } else {
+ if (key.compare(this.searchCriteria.getSearchString(), true) != 0) {
+ return true;
+ }
+ }
+ }
+
+ return acceptResult(address);
+ }
+
+ protected abstract boolean acceptResult(long address);
+ }
+
+ private FieldSearchIndex(FieldSearchKey<?> searchKey) {
+ this.btreeFactory = BTree.getFactory(new IBTreeComparator() {
+ @Override
+ public int compare(Nd nd, long record1, long record2) {
+ IString key1 = FieldSearchIndex.this.searchKey.get(nd, record1);
+ IString key2 = FieldSearchIndex.this.searchKey.get(nd, record2);
+
+ int cmp = key1.compareCompatibleWithIgnoreCase(key2);
+ if (cmp == 0) {
+ cmp = Long.signum(record1 - record2);
+ }
+
+ return cmp;
+ }
+ });
+
+ if (searchKey != null) {
+ if (searchKey.searchIndex != null && searchKey.searchIndex != this) {
+ throw new IllegalArgumentException(
+ "Attempted to construct a FieldSearchIndex referring to a search key that " //$NON-NLS-1$
+ + "is already in use by a different index"); //$NON-NLS-1$
+ }
+ searchKey.searchIndex = this;
+ }
+ this.searchKey = searchKey;
+ }
+
+ public static <T extends NdNode, B> FieldSearchIndex<T> create(StructDef<B> builder,
+ final FieldSearchKey<B> searchKey) {
+
+ FieldSearchIndex<T> result = new FieldSearchIndex<T>(searchKey);
+
+ builder.add(result);
+ builder.addDestructableField(result);
+
+ return result;
+ }
+
+ public BTree get(Nd nd, long address) {
+ return this.btreeFactory.create(nd, address + this.offset);
+ }
+
+ @Override
+ public void destruct(Nd nd, long address) {
+ this.btreeFactory.destruct(nd, address);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return this.btreeFactory.getRecordSize();
+ }
+
+ public T findFirst(final Nd nd, long address, final SearchCriteria searchCriteria) {
+ return findBest(nd, address, searchCriteria, anything);
+ }
+
+ @SuppressWarnings("unchecked")
+ public T findBest(final Nd nd, long address, final SearchCriteria searchCriteria, final IResultRank rankFunction) {
+ final long[] resultRank = new long[1];
+ final long[] result = new long[1];
+ get(nd, address).accept(new SearchCriteriaToBtreeVisitorAdapter(searchCriteria, nd) {
+ @Override
+ protected boolean acceptResult(long resultAddress) {
+ long rank = rankFunction.getRank(nd, resultAddress);
+ if (rank >= resultRank[0]) {
+ resultRank[0] = rank;
+ result[0] = resultAddress;
+ }
+ return true;
+ }
+ });
+
+ if (result[0] == 0) {
+ return null;
+ }
+ return (T)NdNode.load(nd, result[0]);
+ }
+
+ public interface Visitor<T> {
+ boolean visit(T toVisit);
+ }
+
+ public boolean visitAll(final Nd nd, long address, final SearchCriteria searchCriteria, final Visitor<T> visitor) {
+ return get(nd, address).accept(new SearchCriteriaToBtreeVisitorAdapter(searchCriteria, nd) {
+ @SuppressWarnings("unchecked")
+ @Override
+ protected boolean acceptResult(long resultAddress) {
+ return visitor.visit((T)NdNode.load(nd, resultAddress));
+ }
+ });
+ }
+
+ public List<T> findAll(final Nd nd, long address, final SearchCriteria searchCriteria) {
+ final List<T> result = new ArrayList<T>();
+ get(nd, address).accept(new SearchCriteriaToBtreeVisitorAdapter(searchCriteria, nd) {
+ @SuppressWarnings("unchecked")
+ @Override
+ protected boolean acceptResult(long resultAddress) {
+ result.add((T)NdNode.load(nd, resultAddress));
+ return true;
+ }
+ });
+
+ return result;
+ }
+
+ /**
+ * Returns the entire contents of the index as a single list.
+ */
+ public List<T> asList(final Nd nd, long address) {
+ final List<T> result = new ArrayList<T>();
+ get(nd, address).accept(new IBTreeVisitor() {
+ @Override
+ public int compare(long record) {
+ return 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean visit(long resultAddress) {
+ result.add((T)NdNode.load(nd, resultAddress));
+ return true;
+ }
+ });
+
+ return result;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchKey.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchKey.java
new file mode 100644
index 000000000..1b585cb5d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldSearchKey.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.BTree;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.EmptyString;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+
+/**
+ * Represents a search key into a global search index.
+ */
+public class FieldSearchKey<T> implements IField, IDestructableField {
+ private int offset;
+ FieldSearchIndex<?> searchIndex;
+
+ private FieldSearchKey(FieldSearchIndex<?> searchIndex) {
+ if (searchIndex != null) {
+ if (searchIndex.searchKey != null && searchIndex.searchKey != this) {
+ throw new IllegalArgumentException(
+ "Attempted to construct a FieldSearchKey referring to a search index that is " //$NON-NLS-1$
+ + "already in use by a different key"); //$NON-NLS-1$
+ }
+ searchIndex.searchKey = this;
+ }
+ this.searchIndex = searchIndex;
+ }
+
+ /**
+ * Creates a search key attribute in the given struct which stores an entry in the given global search index
+ */
+ public static <T, B extends NdNode> FieldSearchKey<T> create(StructDef<B> builder,
+ FieldSearchIndex<B> searchIndex) {
+ FieldSearchKey<T> result = new FieldSearchKey<T>(searchIndex);
+
+ builder.add(result);
+ builder.addDestructableField(result);
+
+ return result;
+ }
+
+ public void put(Nd nd, long address, String newString) {
+ put(nd, address, newString.toCharArray());
+ }
+
+ /**
+ * Sets the value of the key and inserts it into the index if it is not already present
+ */
+ public void put(Nd nd, long address, char[] newString) {
+ cleanup(nd, address);
+
+ Database db = nd.getDB();
+ BTree btree = this.searchIndex.get(nd, Database.DATA_AREA_OFFSET);
+ db.putRecPtr(address + this.offset, db.newString(newString).getRecord());
+ btree.insert(address);
+ }
+
+ public IString get(Nd nd, long address) {
+ Database db = nd.getDB();
+ long namerec = db.getRecPtr(address + this.offset);
+
+ if (namerec == 0) {
+ return EmptyString.create();
+ }
+ return db.getString(namerec);
+ }
+
+ @Override
+ public void destruct(Nd nd, long address) {
+ cleanup(nd, address);
+ }
+
+ private void cleanup(Nd nd, long address) {
+ boolean isInIndex = isInIndex(nd, address);
+
+ if (isInIndex) {
+ // Remove this entry from the search index
+ this.searchIndex.get(nd, Database.DATA_AREA_OFFSET).delete(address);
+
+ get(nd, address).delete();
+ nd.getDB().putRecPtr(address + this.offset, 0);
+ }
+ }
+
+ /**
+ * Clears this key and removes it from the search index
+ */
+ public void removeFromIndex(Nd nd, long address) {
+ cleanup(nd, address);
+ }
+
+ /**
+ * Returns true iff this key is currently in the index
+ */
+ public boolean isInIndex(Nd nd, long address) {
+ long fieldAddress = address + this.offset;
+ Database db = nd.getDB();
+ long namerec = db.getRecPtr(fieldAddress);
+
+ boolean isInIndex = namerec != 0;
+ return isInIndex;
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return FieldString.RECORD_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldShort.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldShort.java
new file mode 100644
index 000000000..fe2a56b2a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldShort.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+
+/**
+ * Declares a Nd field of type short. Can be used in place of {@link Field}&lt{@link Short}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldShort implements IField {
+ private int offset;
+
+ public FieldShort() {
+ }
+
+ public short get(Nd nd, long address) {
+ Database db = nd.getDB();
+ return db.getShort(address + this.offset);
+ }
+
+ public void put(Nd nd, long address, short newValue) {
+ nd.getDB().putShort(address + this.offset, newValue);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return Database.SHORT_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldString.java
new file mode 100644
index 000000000..ddd449380
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/FieldString.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.EmptyString;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+
+/**
+ * Declares a Nd field of type string. Can be used in place of {@link Field}&lt{@link String}&gt in order to
+ * avoid extra GC overhead.
+ */
+public class FieldString implements IDestructableField, IField {
+ public static final int RECORD_SIZE = Database.STRING_SIZE;
+ private static final char[] EMPTY_CHAR_ARRAY = new char[0];
+ private int offset;
+
+ public FieldString() {
+ }
+
+ public IString get(Nd nd, long address) {
+ Database db = nd.getDB();
+ long namerec = db.getRecPtr(address + this.offset);
+
+ if (namerec == 0) {
+ return EmptyString.create();
+ }
+ return db.getString(namerec);
+ }
+
+ public void put(Nd nd, long address, char[] newString) {
+ if (newString == null) {
+ newString = EMPTY_CHAR_ARRAY;
+ }
+ final Database db= nd.getDB();
+ IString name= get(nd, address);
+ if (name.compare(newString, true) != 0) {
+ name.delete();
+ if (newString != null && newString.length > 0) {
+ db.putRecPtr(address + this.offset, db.newString(newString).getRecord());
+ } else {
+ db.putRecPtr(address + this.offset, 0);
+ }
+ }
+ }
+
+ public void put(Nd nd, long address, String newString) {
+ put(nd, address, newString.toCharArray());
+ }
+
+ public void destruct(Nd nd, long address) {
+ get(nd, address).delete();
+ nd.getDB().putRecPtr(address + this.offset, 0);
+ }
+
+ @Override
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return RECORD_SIZE;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IDestructableField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IDestructableField.java
new file mode 100644
index 000000000..12d216c94
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IDestructableField.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+public interface IDestructableField {
+ public void destruct(Nd nd, long address);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java
new file mode 100644
index 000000000..979a0eb7d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IField.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+public interface IField {
+ void setOffset(int offset);
+ int getRecordSize();
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IRefCountedField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IRefCountedField.java
new file mode 100644
index 000000000..89c27838a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/IRefCountedField.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+public interface IRefCountedField {
+ /**
+ * Returns true if this field knows of any remaining incoming references to this object. This is
+ * used by the implementation of {@link FieldManyToOne} to determine whether or not
+ * a refcounted object should be deleted after a reference is removed.
+ * <p>
+ * Implementations should return false if the refcount is 0 or true if the refcount
+ * is nonzero.
+ */
+ public boolean hasReferences(Nd nd, long address);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java
new file mode 100644
index 000000000..261e85371
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/field/StructDef.java
@@ -0,0 +1,398 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.field;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.IDestructable;
+import org.eclipse.jdt.internal.core.nd.ITypeFactory;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Defines a data structure that will appear in the database.
+ * <p>
+ * There are three mechanisms for deleting a struct from the database:
+ * <ul>
+ * <li>Explicit deletion. This happens synchronously via manual calls to Nd.delete. Structs intended for manual
+ * deletion have refCounted=false and an empty ownerFields.
+ * <li>Owner pointers. Such structs have one or more outbound pointers to an "owner" object. They are deleted
+ * asynchronously when the last owner pointer is deleted. The structs have refCounted=false and a nonempty
+ * ownerFields.
+ * <li>Refcounting. Such structs are deleted asynchronously when all elements are removed from all of their ManyToOne
+ * relationships which are not marked as incoming owner pointers. Owner relationships need to be excluded from
+ * refcounting since they would always create cycles. These structs have refCounted=true.
+ * </ul>
+ * <p>
+ * Structs deleted by refcounting and owner pointers are not intended to inherit from one another, but anything may
+ * inherit from a struct that uses manual deletion and anything may inherit from a struct that uses the same deletion
+ * mechanism.
+ */
+public final class StructDef<T> {
+ Class<T> clazz;
+ private StructDef<? super T> superClass;
+ private List<IField> fields = new ArrayList<>();
+ private boolean doneCalled;
+ private boolean offsetsComputed;
+ private List<StructDef<? extends T>> subClasses = new ArrayList<>();
+ private int size;
+ List<IDestructableField> destructableFields = new ArrayList<>();
+ boolean refCounted;
+ private List<IRefCountedField> refCountedFields = new ArrayList<>();
+ private List<IRefCountedField> ownerFields = new ArrayList<>();
+ boolean isAbstract;
+ private ITypeFactory<T> factory;
+ protected boolean hasUserDestructor;
+ private DeletionSemantics deletionSemantics;
+
+ public static enum DeletionSemantics {
+ EXPLICIT, OWNED, REFCOUNTED
+ }
+
+ private StructDef(Class<T> clazz) {
+ this(clazz, null);
+ }
+
+ private StructDef(Class<T> clazz, StructDef<? super T> superClass) {
+ this(clazz, superClass, Modifier.isAbstract(clazz.getModifiers()));
+ }
+
+ private StructDef(Class<T> clazz, StructDef<? super T> superClass, boolean isAbstract) {
+ this.clazz = clazz;
+ this.superClass = superClass;
+ if (this.superClass != null) {
+ this.superClass.subClasses.add(this);
+ }
+ this.isAbstract = isAbstract;
+ final String fullyQualifiedClassName = clazz.getName();
+
+ final Constructor<T> constructor;
+ if (!this.isAbstract) {
+ try {
+ constructor = clazz.getConstructor(new Class<?>[] { Nd.class, long.class });
+ } catch (NoSuchMethodException | SecurityException e) {
+ throw new IllegalArgumentException("The node class " + fullyQualifiedClassName //$NON-NLS-1$
+ + " does not have an appropriate constructor for it to be used with Nd"); //$NON-NLS-1$
+ }
+ } else {
+ constructor = null;
+ }
+
+ this.hasUserDestructor = IDestructable.class.isAssignableFrom(clazz);
+
+ this.factory = new ITypeFactory<T>() {
+ public T create(Nd dom, long address) {
+ if (StructDef.this.isAbstract) {
+ throw new UnsupportedOperationException(
+ "Attempting to instantiate abstract class" + fullyQualifiedClassName); //$NON-NLS-1$
+ }
+
+ try {
+ return constructor.newInstance(dom, address);
+ } catch (InvocationTargetException e) {
+ Throwable target = e.getCause();
+
+ if (target instanceof RuntimeException) {
+ throw (RuntimeException) target;
+ }
+
+ throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$
+ }
+ }
+
+ public int getRecordSize() {
+ return StructDef.this.size();
+ }
+
+ public boolean hasDestructor() {
+ return StructDef.this.hasUserDestructor || hasDestructableFields();
+ }
+
+ public Class<?> getElementClass() {
+ return StructDef.this.clazz;
+ }
+
+ public void destruct(Nd nd, long address) {
+ checkNotMutable();
+ if (StructDef.this.hasUserDestructor) {
+ IDestructable destructable = (IDestructable)create(nd, address);
+ destructable.destruct();
+ }
+ destructFields(nd, address);
+ }
+
+ public void destructFields(Nd dom, long address) {
+ StructDef.this.destructFields(dom, address);
+ }
+
+ @Override
+ public boolean isReadyForDeletion(Nd dom, long address) {
+ return StructDef.this.isReadyForDeletion(dom, address);
+ }
+
+ @Override
+ public DeletionSemantics getDeletionSemantics() {
+ return StructDef.this.getDeletionSemantics();
+ }
+ };
+ }
+
+ public Class<T> getStructClass() {
+ return this.clazz;
+ }
+
+ @Override
+ public String toString() {
+ return this.clazz.getName();
+ }
+
+ public static <T> StructDef<T> createAbstract(Class<T> clazz) {
+ return new StructDef<T>(clazz, null, true);
+ }
+
+ public static <T> StructDef<T> createAbstract(Class<T> clazz, StructDef<? super T> superClass) {
+ return new StructDef<T>(clazz, superClass, true);
+ }
+
+ public static <T> StructDef<T> create(Class<T> clazz) {
+ return new StructDef<T>(clazz);
+ }
+
+ public static <T> StructDef<T> create(Class<T> clazz, StructDef<? super T> superClass) {
+ return new StructDef<T>(clazz, superClass);
+ }
+
+ protected boolean isReadyForDeletion(Nd dom, long address) {
+ List<IRefCountedField> toIterate = Collections.EMPTY_LIST;
+ switch (this.deletionSemantics) {
+ case EXPLICIT: return false;
+ case OWNED: toIterate = this.ownerFields; break;
+ case REFCOUNTED: toIterate = this.refCountedFields; break;
+ }
+
+ for (IRefCountedField next : toIterate) {
+ if (next.hasReferences(dom, address)) {
+ return false;
+ }
+ }
+
+ final StructDef<? super T> localSuperClass = StructDef.this.superClass;
+ if (localSuperClass != null && localSuperClass.deletionSemantics != DeletionSemantics.EXPLICIT) {
+ return localSuperClass.isReadyForDeletion(dom, address);
+ }
+ return true;
+ }
+
+ protected boolean hasDestructableFields() {
+ return (!StructDef.this.destructableFields.isEmpty() ||
+ (StructDef.this.superClass != null && StructDef.this.superClass.hasDestructableFields()));
+ }
+
+ public DeletionSemantics getDeletionSemantics() {
+ return this.deletionSemantics;
+ }
+
+ /**
+ * Call this once all the fields have been added to the struct definition and it is
+ * ready to use.
+ */
+ public void done() {
+ if (this.doneCalled) {
+ throw new IllegalStateException("May not call done() more than once"); //$NON-NLS-1$
+ }
+ this.doneCalled = true;
+
+ if (this.superClass == null || this.superClass.areOffsetsComputed()) {
+ computeOffsets();
+ }
+ }
+
+ public void add(IField toAdd) {
+ checkMutable();
+
+ this.fields.add(toAdd);
+ }
+
+ public void addDestructableField(IDestructableField field) {
+ checkMutable();
+
+ this.destructableFields.add(field);
+ }
+
+ public StructDef<T> useStandardRefCounting() {
+ checkMutable();
+
+ this.refCounted = true;
+ return this;
+ }
+
+ public void addRefCountedField(IRefCountedField result) {
+ checkMutable();
+
+ this.refCountedFields.add(result);
+ }
+
+ public void addOwnerField(IRefCountedField result) {
+ checkMutable();
+
+ this.ownerFields.add(result);
+ }
+
+ public boolean areOffsetsComputed() {
+ return this.offsetsComputed;
+ }
+
+ public int size() {
+ checkNotMutable();
+ return this.size;
+ }
+
+ void checkNotMutable() {
+ if (!this.offsetsComputed) {
+ throw new IllegalStateException("Must call done() before using the struct"); //$NON-NLS-1$
+ }
+ }
+
+ private void checkMutable() {
+ if (this.doneCalled) {
+ throw new IllegalStateException("May not modify a StructDef after done() has been called"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Invoked on all StructDef after both {@link #done()} has been called on the struct and
+ * {@link #computeOffsets()} has been called on their base class.
+ */
+ private void computeOffsets() {
+ int offset = this.superClass == null ? 0 : this.superClass.size();
+
+ for (IField next : this.fields) {
+ next.setOffset(offset);
+ offset += next.getRecordSize();
+ }
+
+ this.size = offset;
+ if (this.refCounted) {
+ this.deletionSemantics = DeletionSemantics.REFCOUNTED;
+ } else {
+ if (!this.ownerFields.isEmpty()) {
+ this.deletionSemantics = DeletionSemantics.OWNED;
+ } else if (this.superClass != null) {
+ this.deletionSemantics = this.superClass.deletionSemantics;
+ } else {
+ this.deletionSemantics = DeletionSemantics.EXPLICIT;
+ }
+ }
+ // Now verify that the deletion semantics of this struct are compatible with the deletion
+ // semantics of its superclass
+ if (this.superClass != null && this.deletionSemantics != this.superClass.deletionSemantics) {
+ if (this.superClass.deletionSemantics != DeletionSemantics.EXPLICIT) {
+ throw new IllegalStateException("A class (" + this.clazz.getName() + ") that uses " //$NON-NLS-1$//$NON-NLS-2$
+ + this.deletionSemantics.toString() + " deletion semantics may not inherit from a class " //$NON-NLS-1$
+ + "that uses " + this.superClass.deletionSemantics.toString() + " semantics"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+
+ this.offsetsComputed = true;
+
+ for (StructDef<? extends T> next : this.subClasses) {
+ if (next.doneCalled) {
+ next.computeOffsets();
+ }
+ }
+ }
+
+ public FieldPointer addPointer() {
+ FieldPointer result = new FieldPointer();
+ add(result);
+ return result;
+ }
+
+ public FieldShort addShort() {
+ FieldShort result = new FieldShort();
+ add(result);
+ return result;
+ }
+
+ public FieldInt addInt() {
+ FieldInt result = new FieldInt();
+ add(result);
+ return result;
+ }
+
+ public FieldLong addLong() {
+ FieldLong result = new FieldLong();
+ add(result);
+ return result;
+ }
+
+ public FieldString addString() {
+ FieldString result = new FieldString();
+ add(result);
+ addDestructableField(result);
+ return result;
+ }
+
+ public FieldDouble addDouble() {
+ FieldDouble result = new FieldDouble();
+ add(result);
+ return result;
+ }
+
+ public FieldFloat addFloat() {
+ FieldFloat result = new FieldFloat();
+ add(result);
+ return result;
+ }
+
+ public FieldByte addByte() {
+ FieldByte result = new FieldByte();
+ add(result);
+ return result;
+ }
+
+ public FieldChar addChar() {
+ FieldChar result = new FieldChar();
+ add(result);
+ return result;
+ }
+
+ public <F> Field<F> add(ITypeFactory<F> factory1) {
+ Field<F> result = new Field<>(factory1);
+ add(result);
+ if (result.factory.hasDestructor()) {
+ this.destructableFields.add(result);
+ }
+ return result;
+ }
+
+ public ITypeFactory<T> getFactory() {
+ return this.factory;
+ }
+
+ void destructFields(Nd dom, long address) {
+ for (IDestructableField next : StructDef.this.destructableFields) {
+ next.destruct(dom, address);
+ }
+
+ if (this.superClass != null) {
+ this.superClass.destructFields(dom, address);
+ }
+ }
+
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/BindingToIndexConverter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/BindingToIndexConverter.java
new file mode 100644
index 000000000..a8788eb9b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/BindingToIndexConverter.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.dom.IAnnotationBinding;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
+import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.IPackageBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
+import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdTreeNode;
+import org.eclipse.jdt.internal.core.nd.java.NdType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeInterface;
+
+public class BindingToIndexConverter {
+ private static final boolean ENABLE_LOGGING = false;
+ private JavaIndex index;
+ private NdResourceFile resource;
+
+ public BindingToIndexConverter(NdResourceFile resource) {
+ this.resource = resource;
+ this.index = JavaIndex.getIndex(resource.getNd());
+ }
+
+ public void addBinding(NdTreeNode parent, IBinding binding, IProgressMonitor monitor) {
+ switch (binding.getKind()) {
+ case IBinding.TYPE:
+ addType((ITypeBinding) binding, monitor);
+ break;
+ case IBinding.ANNOTATION:
+ addAnnotation(parent, (IAnnotationBinding) binding, monitor);
+ break;
+ case IBinding.METHOD:
+ addMethod(parent, (IMethodBinding) binding, monitor);
+ break;
+ case IBinding.VARIABLE:
+ addVariable(parent, (IVariableBinding) binding, monitor);
+ break;
+ case IBinding.PACKAGE:
+ addPackage(parent, (IPackageBinding) binding, monitor);
+ break;
+ case IBinding.MEMBER_VALUE_PAIR:
+ addMemberValuePair(parent, (IMemberValuePairBinding) binding, monitor);
+ break;
+ default:
+ Package.log("Encountered unknown binding type: " + binding.getKind(), null); //$NON-NLS-1$
+ }
+ }
+
+ public void addMemberValuePair(NdTreeNode parent, IMemberValuePairBinding binding, IProgressMonitor monitor) {
+ logInfo("Adding member value pair: " + binding.getName()); //$NON-NLS-1$
+ }
+
+ public void addPackage(NdTreeNode parent, IPackageBinding binding, IProgressMonitor monitor) {
+ logInfo("Adding package: " + binding.getName()); //$NON-NLS-1$
+ }
+
+ public void addVariable(NdTreeNode parent, IVariableBinding binding, IProgressMonitor monitor) {
+ logInfo("Adding variable: " + binding.getName()); //$NON-NLS-1$
+ }
+
+ public void addMethod(NdTreeNode parent, IMethodBinding binding, IProgressMonitor monitor) {
+ logInfo("Adding method: " + binding.getName()); //$NON-NLS-1$
+ }
+
+ public void addAnnotation(NdTreeNode parent, IAnnotationBinding binding, IProgressMonitor monitor) {
+ logInfo("Adding annotation: " + binding.getName()); //$NON-NLS-1$
+ }
+
+ public NdType addType(ITypeBinding binding, IProgressMonitor monitor) {
+ logInfo("Adding type: " + binding.getBinaryName()); //$NON-NLS-1$
+
+ NdTypeId name = makeTypeId(binding);
+ NdType type = name.findTypeByResourceAddress(this.resource.address);
+
+ if (type == null) {
+ type = new NdType(getNd(), this.resource);
+ }
+
+ type.setTypeId(name);
+
+ ITypeBinding superclass = binding.getSuperclass();
+
+ if (superclass != null) {
+ type.setSuperclass(makeTypeId(superclass));
+ }
+
+ for (ITypeBinding next : binding.getInterfaces()) {
+ new NdTypeInterface(getNd(), type, makeTypeId(next));
+ }
+
+ return type;
+ }
+
+ private void logInfo(String string) {
+ if (ENABLE_LOGGING) {
+ Package.logInfo(string);
+ }
+ }
+
+ private NdTypeId makeTypeId(ITypeBinding forBinding) {
+ return this.index.createTypeId(JavaNames.binaryNameToFieldDescriptor(forBinding.getBinaryName().toCharArray()));
+ }
+
+ private Nd getNd() {
+ return this.resource.getNd();
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
new file mode 100644
index 000000000..afd740ee0
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/ClassFileToIndexConverter.java
@@ -0,0 +1,930 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.compiler.env.ClassSignature;
+import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.lookup.SignatureWrapper;
+import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.Openable;
+import org.eclipse.jdt.internal.core.PackageFragment;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotation;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInConstant;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInMethod;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInMethodParameter;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInType;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotationInVariable;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotationValuePair;
+import org.eclipse.jdt.internal.core.nd.java.NdBinding;
+import org.eclipse.jdt.internal.core.nd.java.NdComplexTypeSignature;
+import org.eclipse.jdt.internal.core.nd.java.NdConstant;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantAnnotation;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantArray;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantClass;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantEnum;
+import org.eclipse.jdt.internal.core.nd.java.NdMethod;
+import org.eclipse.jdt.internal.core.nd.java.NdMethodException;
+import org.eclipse.jdt.internal.core.nd.java.NdMethodId;
+import org.eclipse.jdt.internal.core.nd.java.NdMethodParameter;
+import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotation;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotationInMethod;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotationInType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotationInVariable;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeArgument;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeBound;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeInterface;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeParameter;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeSignature;
+import org.eclipse.jdt.internal.core.nd.java.NdVariable;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+import org.eclipse.jdt.internal.core.util.Util;
+
+public final class ClassFileToIndexConverter {
+ private static final char[] JAVA_LANG_OBJECT_FIELD_DESCRIPTOR = "Ljava/lang/Object;".toCharArray(); //$NON-NLS-1$
+ private static final char[] INNER_TYPE_SEPARATOR = new char[] { '$' };
+ private static final char[] FIELD_DESCRIPTOR_SUFFIX = new char[] { ';' };
+ private static final char[] COMMA = new char[]{','};
+ private static final char[][] EMPTY_CHAR_ARRAY_ARRAY = new char[0][];
+ private static final boolean ENABLE_LOGGING = false;
+ private static final char[] EMPTY_CHAR_ARRAY = new char[0];
+ private static final char[] PATH_SEPARATOR = new char[]{'/'};
+ private static final char[] ARRAY_FIELD_DESCRIPTOR_PREFIX = new char[] { '[' };
+ private NdResourceFile resource;
+ private JavaIndex index;
+
+ public ClassFileToIndexConverter(NdResourceFile resourceFile) {
+ this.resource = resourceFile;
+ this.index = JavaIndex.getIndex(resourceFile.getNd());
+ }
+
+ private Nd getNd() {
+ return this.resource.getNd();
+ }
+
+ public static IBinaryType getTypeFromClassFile(IClassFile iClassFile, IProgressMonitor monitor)
+ throws CoreException, ClassFormatException {
+ BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(iClassFile);
+ return BinaryTypeFactory.rawReadType(descriptor, true);
+ }
+
+ /**
+ * Create a type info from the given class file in a jar and adds it to the given list of infos.
+ *
+ * @throws CoreException
+ */
+ protected static IBinaryType createInfoFromClassFileInJar(Openable classFile) throws CoreException {
+ PackageFragment pkg = (PackageFragment) classFile.getParent();
+ String classFilePath = Util.concatWith(pkg.names, classFile.getElementName(), '/');
+ IBinaryType info = null;
+ java.util.zip.ZipFile zipFile = null;
+ try {
+ zipFile = ((JarPackageFragmentRoot) pkg.getParent()).getJar();
+ info = org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader.read(zipFile, classFilePath);
+ } catch (Exception e) {
+ throw new CoreException(Package.createStatus("Unable to parse JAR file", e)); //$NON-NLS-1$
+ } finally {
+ JavaModelManager.getJavaModelManager().closeZipFile(zipFile);
+ }
+ return info;
+ }
+
+ /**
+ * Adds a type to the index, given an input class file and a binary name. Note that the given binary name is
+ *
+ * @param binaryType an object used for parsing the .class file itself
+ * @param fieldDescriptor the name that is used to locate the class, computed from the .class file's name and location.
+ * In the event that the .class file has been moved, this may differ from the binary name stored in the .class file
+ * itself, which is why this is received as an argument rather than extracted from the .class file.
+ * @throws CoreException
+ */
+ public NdType addType(IBinaryType binaryType, char[] fieldDescriptor, IProgressMonitor monitor) throws CoreException {
+ char[] fieldDescriptorFromClass = JavaNames.binaryNameToFieldDescriptor(binaryType.getName());
+ logInfo("adding binary type " + new String(fieldDescriptor)); //$NON-NLS-1$
+
+ NdTypeId name = createTypeIdFromFieldDescriptor(fieldDescriptor);
+ NdType type = name.findTypeByResourceAddress(this.resource.address);
+
+ if (type == null) {
+ type = new NdType(getNd(), this.resource);
+ }
+
+ IBinaryTypeAnnotation[] typeAnnotations = binaryType.getTypeAnnotations();
+ if (typeAnnotations != null) {
+ for (IBinaryTypeAnnotation typeAnnotation : typeAnnotations) {
+ NdTypeAnnotationInType annotation = new NdTypeAnnotationInType(getNd(), type);
+
+ initTypeAnnotation(annotation, typeAnnotation);
+ }
+ }
+
+ type.setTypeId(name);
+
+ if (!CharArrayUtils.equals(fieldDescriptorFromClass, fieldDescriptor)) {
+ type.setFieldDescriptorFromClass(fieldDescriptorFromClass);
+ }
+
+ char[][] interfaces = binaryType.getInterfaceNames();
+ if (interfaces == null) {
+ interfaces = EMPTY_CHAR_ARRAY_ARRAY;
+ }
+
+ if (binaryType.getGenericSignature() != null) {
+ type.setFlag(NdType.FLG_GENERIC_SIGNATURE_PRESENT, true);
+ }
+
+ // Create the default generic signature if the .class file didn't supply one
+ SignatureWrapper signatureWrapper = GenericSignatures.getGenericSignature(binaryType);
+
+ type.setModifiers(binaryType.getModifiers());
+ type.setDeclaringType(createTypeIdFromBinaryName(binaryType.getEnclosingTypeName()));
+
+ readTypeParameters(type, signatureWrapper);
+
+ char[] superclassFieldDescriptor;
+ char[] superclassBinaryName = binaryType.getSuperclassName();
+ if (superclassBinaryName == null) {
+ superclassFieldDescriptor = JAVA_LANG_OBJECT_FIELD_DESCRIPTOR;
+ } else {
+ superclassFieldDescriptor = JavaNames.binaryNameToFieldDescriptor(superclassBinaryName);
+ }
+ type.setSuperclass(createTypeSignature(signatureWrapper, superclassFieldDescriptor));
+
+ short interfaceIdx = 0;
+ while (signatureWrapper.start < signatureWrapper.signature.length) {
+ // Note that there may be more interfaces listed in the generic signature than in the interfaces list.
+ // Although the VM spec doesn't discuss this case specifically, there are .class files in the wild with
+ // this characteristic. In such cases, we take what's in the generic signature and discard what's in the
+ // interfaces list.
+ char[] interfaceSpec = interfaceIdx < interfaces.length ? interfaces[interfaceIdx] : EMPTY_CHAR_ARRAY;
+ new NdTypeInterface(getNd(), type,
+ createTypeSignature(signatureWrapper, JavaNames.binaryNameToFieldDescriptor(interfaceSpec)));
+ interfaceIdx++;
+ }
+
+ IBinaryAnnotation[] annotations = binaryType.getAnnotations();
+ attachAnnotations(type, annotations);
+
+ type.setDeclaringMethod(createMethodId(binaryType.getEnclosingTypeName(), binaryType.getEnclosingMethod()));
+
+ IBinaryField[] fields = binaryType.getFields();
+
+ if (fields != null) {
+ for (IBinaryField nextField : fields) {
+ addField(type, nextField);
+ }
+ }
+
+ IBinaryMethod[] methods = binaryType.getMethods();
+
+ if (methods != null) {
+ for (IBinaryMethod next : methods) {
+ addMethod(type, next, binaryType);
+ }
+ }
+
+ char[][][] missingTypeNames = binaryType.getMissingTypeNames();
+ char[] missingTypeString = getMissingTypeString(missingTypeNames);
+
+ type.setMissingTypeNames(missingTypeString);
+ type.setSourceFileName(binaryType.sourceFileName());
+ type.setAnonymous(binaryType.isAnonymous());
+ type.setIsLocal(binaryType.isLocal());
+ type.setIsMember(binaryType.isMember());
+ type.setTagBits(binaryType.getTagBits());
+ type.setSourceNameOverride(binaryType.getSourceName());
+
+ return type;
+ }
+
+ private static char[] getMissingTypeString(char[][][] missingTypeNames) {
+ char[] missingTypeString = null;
+ if (missingTypeNames != null) {
+ CharArrayBuffer builder = new CharArrayBuffer();
+ for (int typeIdx = 0; typeIdx < missingTypeNames.length; typeIdx++) {
+ char[][] next = missingTypeNames[typeIdx];
+ if (typeIdx != 0) {
+ builder.append(COMMA);
+ }
+ if (next == null) {
+ continue;
+ }
+ for (int segmentIdx = 0; segmentIdx < next.length; segmentIdx++) {
+ char[] segment = next[segmentIdx];
+ if (segment == null) {
+ continue;
+ }
+ if (segmentIdx != 0) {
+ builder.append(PATH_SEPARATOR);
+ }
+ builder.append(segment);
+ }
+ }
+ missingTypeString = builder.getContents();
+ }
+ return missingTypeString;
+ }
+
+ private void attachAnnotations(NdMethod method, IBinaryAnnotation[] annotations) {
+ if (annotations != null) {
+ for (IBinaryAnnotation next : annotations) {
+ NdAnnotationInMethod annotation = new NdAnnotationInMethod(getNd(), method);
+ initAnnotation(annotation, next);
+ }
+ }
+ }
+
+ private void attachAnnotations(NdType type, IBinaryAnnotation[] annotations) {
+ if (annotations != null) {
+ for (IBinaryAnnotation next : annotations) {
+ NdAnnotationInType annotation = new NdAnnotationInType(getNd(), type);
+ initAnnotation(annotation, next);
+ }
+ }
+ }
+
+ private void attachAnnotations(NdVariable variable, IBinaryAnnotation[] annotations) {
+ if (annotations != null) {
+ for (IBinaryAnnotation next : annotations) {
+ NdAnnotationInVariable annotation = new NdAnnotationInVariable(getNd(), variable);
+ initAnnotation(annotation, next);
+ }
+ }
+ }
+
+ private void attachAnnotations(NdMethodParameter variable, IBinaryAnnotation[] annotations) {
+ if (annotations != null) {
+ for (IBinaryAnnotation next : annotations) {
+ NdAnnotationInMethodParameter annotation = new NdAnnotationInMethodParameter(getNd(), variable);
+ initAnnotation(annotation, next);
+ }
+ }
+ }
+
+ /**
+ * Adds the given method to the given type
+ *
+ * @throws CoreException
+ */
+ private void addMethod(NdType type, IBinaryMethod next, IBinaryType binaryType)
+ throws CoreException {
+ int flags = 0;
+ NdMethod method = new NdMethod(type);
+
+ attachAnnotations(method, next.getAnnotations());
+
+ if (next.getGenericSignature() != null) {
+ flags |= NdMethod.FLG_GENERIC_SIGNATURE_PRESENT;
+ }
+ SignatureWrapper signature = GenericSignatures.getGenericSignature(next);
+ SignatureWrapper descriptor = new SignatureWrapper(next.getMethodDescriptor());
+ readTypeParameters(method, signature);
+
+ IBinaryTypeAnnotation[] typeAnnotations = next.getTypeAnnotations();
+ if (typeAnnotations != null) {
+ for (IBinaryTypeAnnotation typeAnnotation : typeAnnotations) {
+ NdTypeAnnotationInMethod annotation = new NdTypeAnnotationInMethod(getNd(), method);
+
+ initTypeAnnotation(annotation, typeAnnotation);
+ }
+ }
+
+ skipChar(signature, '(');
+ skipChar(descriptor, '(');
+
+ List<char[]> parameterFieldDescriptors = new ArrayList<>();
+ while (!descriptor.atEnd()) {
+ if (descriptor.charAtStart() == ')') {
+ skipChar(descriptor, ')');
+ break;
+ }
+ parameterFieldDescriptors.add(readNextFieldDescriptor(descriptor));
+ }
+
+ char[][] parameterNames = next.getArgumentNames();
+ int numArgumentsInGenericSignature = countMethodArguments(signature);
+ int numCompilerDefinedParameters = Math.max(0,
+ parameterFieldDescriptors.size() - numArgumentsInGenericSignature);
+
+ boolean compilerDefinedParametersAreIncludedInSignature = (next.getGenericSignature() == null);
+
+ // If there is no generic signature, then fall back to heuristics based on what we know about
+ // where the java compiler likes to create compiler-defined arguments
+ if (compilerDefinedParametersAreIncludedInSignature) {
+ // Constructors for non-static member types get a compiler-defined first argument
+ if (binaryType.isMember()
+ && next.isConstructor()
+ && ((binaryType.getModifiers() & Modifier.STATIC) == 0)
+ && parameterFieldDescriptors.size() > 0) {
+
+ numCompilerDefinedParameters = 1;
+ } else {
+ numCompilerDefinedParameters = 0;
+ }
+ }
+
+ int parameterNameIdx = 0;
+ int annotatedParametersCount = next.getAnnotatedParametersCount();
+
+ short descriptorParameterIdx = 0;
+ char[] binaryTypeName = binaryType.getName();
+ while (!signature.atEnd()) {
+ if (signature.charAtStart() == ')') {
+ skipChar(signature, ')');
+ break;
+ }
+ char[] nextFieldDescriptor = parameterFieldDescriptors.get(descriptorParameterIdx);
+ /**
+ * True iff this a parameter which is part of the field descriptor but not the generic signature -- that is,
+ * it is a compiler-defined parameter.
+ */
+ boolean isCompilerDefined = descriptorParameterIdx < numCompilerDefinedParameters;
+ SignatureWrapper nextFieldSignature = signature;
+ if (isCompilerDefined && !compilerDefinedParametersAreIncludedInSignature) {
+ nextFieldSignature = new SignatureWrapper(nextFieldDescriptor);
+ }
+ NdMethodParameter parameter = new NdMethodParameter(method,
+ createTypeSignature(nextFieldSignature, nextFieldDescriptor));
+
+ parameter.setCompilerDefined(isCompilerDefined);
+
+ if (descriptorParameterIdx < annotatedParametersCount) {
+ IBinaryAnnotation[] parameterAnnotations = next.getParameterAnnotations(descriptorParameterIdx,
+ binaryTypeName);
+
+ attachAnnotations(parameter, parameterAnnotations);
+ }
+ if (!isCompilerDefined && parameterNames != null && parameterNames.length > parameterNameIdx) {
+ parameter.setName(parameterNames[parameterNameIdx++]);
+ }
+ descriptorParameterIdx++;
+ }
+
+ skipChar(descriptor, ')');
+ char[] nextFieldDescriptor = readNextFieldDescriptor(descriptor);
+ method.setReturnType(createTypeSignature(signature, nextFieldDescriptor));
+
+ boolean hasExceptionsInSignature = hasAnotherException(signature);
+ char[][] exceptionTypes = next.getExceptionTypeNames();
+ if (exceptionTypes == null) {
+ exceptionTypes = CharArrayUtils.EMPTY_ARRAY_OF_CHAR_ARRAYS;
+ }
+ int throwsIdx = 0;
+ if (hasExceptionsInSignature) {
+ while (hasAnotherException(signature)) {
+ signature.start++;
+ new NdMethodException(method, createTypeSignature(signature,
+ JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx])));
+ throwsIdx++;
+ }
+ } else if (exceptionTypes.length != 0) {
+ for (;throwsIdx < exceptionTypes.length; throwsIdx++) {
+ char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx]);
+ SignatureWrapper convertedWrapper = new SignatureWrapper(fieldDescriptor);
+ new NdMethodException(method, createTypeSignature(convertedWrapper,
+ JavaNames.binaryNameToFieldDescriptor(exceptionTypes[throwsIdx])));
+ }
+ }
+
+ if (hasExceptionsInSignature) {
+ flags |= NdMethod.FLG_THROWS_SIGNATURE_PRESENT;
+ }
+
+ Object defaultValue = next.getDefaultValue();
+ if (defaultValue != null) {
+ method.setDefaultValue(createConstantFromMixedType(defaultValue));
+ }
+
+ method.setMethodId(createMethodId(binaryType.getName(), next.getSelector(), next.getMethodDescriptor()));
+ method.setModifiers(next.getModifiers());
+ method.setTagBits(next.getTagBits());
+ method.setFlags(flags);
+ }
+
+ private boolean hasAnotherException(SignatureWrapper signature) {
+ return !signature.atEnd() && signature.charAtStart() == '^';
+ }
+
+ private void skipChar(SignatureWrapper signature, char toSkip) {
+ if (signature.start < signature.signature.length && signature.charAtStart() == toSkip) {
+ signature.start++;
+ }
+ }
+
+ /**
+ * Adds the given field to the given type
+ */
+ private void addField(NdType type, IBinaryField nextField) throws CoreException {
+ NdVariable variable = new NdVariable(type);
+
+ variable.setName(nextField.getName());
+
+ if (nextField.getGenericSignature() != null) {
+ variable.setVariableFlag(NdVariable.FLG_GENERIC_SIGNATURE_PRESENT);
+ }
+
+ attachAnnotations(variable, nextField.getAnnotations());
+
+ variable.setConstant(NdConstant.create(getNd(), nextField.getConstant()));
+ variable.setModifiers(nextField.getModifiers());
+ SignatureWrapper nextTypeSignature = GenericSignatures.getGenericSignatureFor(nextField);
+
+ IBinaryTypeAnnotation[] typeAnnotations = nextField.getTypeAnnotations();
+ if (typeAnnotations != null) {
+ for (IBinaryTypeAnnotation next : typeAnnotations) {
+ NdTypeAnnotationInVariable annotation = new NdTypeAnnotationInVariable(getNd(), variable);
+
+ initTypeAnnotation(annotation, next);
+ }
+ }
+ variable.setType(createTypeSignature(nextTypeSignature, nextField.getTypeName()));
+ variable.setTagBits(nextField.getTagBits());
+
+ // char[] fieldDescriptor = nextField.getTypeName();
+ // // DO NOT SUBMIT:
+ // IBinaryField bf = IndexBinaryType.createBinaryField(variable);
+ }
+
+ /**
+ * Reads and attaches any generic type parameters at the current start position in the given wrapper. Sets
+ * wrapper.start to the character following the type parameters.
+ *
+ * @throws CoreException
+ */
+ private void readTypeParameters(NdBinding type, SignatureWrapper wrapper)
+ throws CoreException {
+ char[] genericSignature = wrapper.signature;
+ if (genericSignature.length == 0 || wrapper.charAtStart() != '<') {
+ return;
+ }
+
+ int indexOfClosingBracket = wrapper.skipAngleContents(wrapper.start) - 1;
+ wrapper.start++;
+ NdTypeParameter parameter = null;
+ while (wrapper.start < indexOfClosingBracket) {
+ int colonPos = CharOperation.indexOf(':', genericSignature, wrapper.start, indexOfClosingBracket);
+
+ if (colonPos > wrapper.start) {
+ char[] identifier = CharOperation.subarray(genericSignature, wrapper.start, colonPos);
+ parameter = new NdTypeParameter(type, identifier);
+ wrapper.start = colonPos + 1;
+ // The first bound is a class as long as it doesn't start with a double-colon
+ parameter.setFirstBoundIsClass(wrapper.charAtStart() != ':');
+ }
+
+ skipChar(wrapper, ':');
+
+ NdTypeSignature boundSignature = createTypeSignature(wrapper, JAVA_LANG_OBJECT_FIELD_DESCRIPTOR);
+
+ new NdTypeBound(parameter, boundSignature);
+ }
+
+ skipChar(wrapper, '>');
+ }
+
+ private char[] readNextFieldDescriptor(SignatureWrapper genericSignature) {
+ int endPosition = findEndOfFieldDescriptor(genericSignature);
+
+ char[] result = CharArrayUtils.subarray(genericSignature.signature, genericSignature.start, endPosition);
+ genericSignature.start = endPosition;
+ return result;
+ }
+
+ private int findEndOfFieldDescriptor(SignatureWrapper genericSignature) {
+ char[] signature = genericSignature.signature;
+
+ if (signature == null || signature.length == 0) {
+ return genericSignature.start;
+ }
+ int current = genericSignature.start;
+ while (current < signature.length) {
+ char firstChar = signature[current];
+ switch (firstChar) {
+ case 'L':
+ case 'T': {
+ return CharArrayUtils.indexOf(';', signature, current, signature.length) + 1;
+ }
+ case '[': {
+ current++;
+ break;
+ }
+ case 'V':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'F':
+ case 'I':
+ case 'J':
+ case 'S':
+ case 'Z':
+ return current + 1;
+ default:
+ throw new IndexException(Package.createStatus("Field descriptor starts with unknown character: " //$NON-NLS-1$
+ + genericSignature.toString()));
+ }
+ }
+ return current;
+ }
+
+ /**
+ * Given a generic signature which is positioned at the open brace for method arguments, this returns the number of
+ * method arguments. The start position of the given signature is not modified.
+ */
+ private int countMethodArguments(SignatureWrapper genericSignature) {
+ SignatureWrapper lookaheadSignature = new SignatureWrapper(genericSignature.signature);
+ lookaheadSignature.start = genericSignature.start;
+ skipChar(lookaheadSignature, '(');
+ int argumentCount = 0;
+ while (!lookaheadSignature.atEnd() && !(lookaheadSignature.charAtStart() == ')')) {
+ switch (lookaheadSignature.charAtStart()) {
+ case 'T': {
+ // Skip the 'T' prefix
+ lookaheadSignature.nextWord();
+ skipChar(lookaheadSignature, ';');
+ argumentCount++;
+ break;
+ }
+ case '[': {
+ // Skip the '[' prefix
+ lookaheadSignature.start++;
+ break;
+ }
+ case 'V':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'F':
+ case 'I':
+ case 'J':
+ case 'S':
+ case 'Z':
+ argumentCount++;
+ lookaheadSignature.start++;
+ break;
+ case 'L':
+ for (;;) {
+ lookaheadSignature.nextWord();
+ lookaheadSignature.start = lookaheadSignature.skipAngleContents(lookaheadSignature.start);
+ char nextChar = lookaheadSignature.charAtStart();
+ if (nextChar == ';') {
+ break;
+ }
+ if (nextChar != '.') {
+ throw new IllegalStateException(
+ "Unknown char in generic signature " + lookaheadSignature.toString()); //$NON-NLS-1$
+ }
+ }
+ skipChar(lookaheadSignature, ';');
+ argumentCount++;
+ break;
+ default:
+ throw new IllegalStateException("Generic signature starts with unknown character: " //$NON-NLS-1$
+ + lookaheadSignature.toString());
+ }
+ }
+ return argumentCount;
+ }
+
+ /**
+ * Reads a type signature from the given {@link SignatureWrapper}, starting at the character pointed to by
+ * wrapper.start. On return, wrapper.start will point to the first character following the type signature. Returns
+ * null if given an empty signature or the signature for the void type.
+ *
+ * @param genericSignature
+ * the generic signature to parse
+ * @param fieldDescriptorIfVariable
+ * the field descriptor to use if the type is a type variable -- or null if unknown (the field descriptor
+ * for java.lang.Object will be used)
+ * @throws CoreException
+ */
+ private NdTypeSignature createTypeSignature(SignatureWrapper genericSignature, char[] fieldDescriptorIfVariable)
+ throws CoreException {
+ char[] signature = genericSignature.signature;
+
+ if (signature == null || signature.length == 0) {
+ return null;
+ }
+
+ char firstChar = genericSignature.charAtStart();
+ switch (firstChar) {
+ case 'T': {
+ // Skip the 'T' prefix
+ genericSignature.start++;
+ NdComplexTypeSignature typeSignature = new NdComplexTypeSignature(getNd());
+ char[] fieldDescriptor = fieldDescriptorIfVariable;
+ if (fieldDescriptor == null) {
+ fieldDescriptor = JAVA_LANG_OBJECT_FIELD_DESCRIPTOR;
+ }
+ typeSignature.setRawType(createTypeIdFromFieldDescriptor(fieldDescriptor));
+ typeSignature.setVariableIdentifier(genericSignature.nextWord());
+ // Skip the trailing semicolon
+ skipChar(genericSignature, ';');
+ return typeSignature;
+ }
+ case '[': {
+ // Skip the '[' prefix
+ genericSignature.start++;
+ char[] nestedFieldDescriptor = null;
+ if (fieldDescriptorIfVariable != null && fieldDescriptorIfVariable.length > 0
+ && fieldDescriptorIfVariable[0] == '[') {
+ nestedFieldDescriptor = CharArrayUtils.subarray(fieldDescriptorIfVariable, 1);
+ }
+ // Determine the array argument type
+ NdTypeSignature elementType = createTypeSignature(genericSignature, nestedFieldDescriptor);
+ char[] computedFieldDescriptor = CharArrayUtils.concat(ARRAY_FIELD_DESCRIPTOR_PREFIX,
+ elementType.getRawType().getFieldDescriptor().getChars());
+ NdTypeId rawType = createTypeIdFromFieldDescriptor(computedFieldDescriptor);
+ // We encode signatures as though they were a one-argument generic type whose element
+ // type is the generic argument.
+ NdComplexTypeSignature typeSignature = new NdComplexTypeSignature(getNd());
+ typeSignature.setRawType(rawType);
+ NdTypeArgument typeArgument = new NdTypeArgument(getNd(), typeSignature);
+ typeArgument.setType(elementType);
+ return typeSignature;
+ }
+ case 'V':
+ genericSignature.start++;
+ return null;
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'F':
+ case 'I':
+ case 'J':
+ case 'S':
+ case 'Z':
+ genericSignature.start++;
+ return createTypeIdFromFieldDescriptor(new char[] { firstChar });
+ case 'L':
+ return parseClassTypeSignature(null, genericSignature);
+ case '+':
+ case '-':
+ case '*':
+ throw new CoreException(Package.createStatus("Unexpected wildcard in top-level of generic signature: " //$NON-NLS-1$
+ + genericSignature.toString()));
+ default:
+ throw new CoreException(Package.createStatus("Generic signature starts with unknown character: " //$NON-NLS-1$
+ + genericSignature.toString()));
+ }
+ }
+
+ /**
+ * Parses a ClassTypeSignature (as described in section 4.7.9.1 of the Java VM Specification Java SE 8 Edition). The
+ * read pointer should be located just after the identifier. The caller is expected to have already read the field
+ * descriptor for the type.
+ */
+ private NdTypeSignature parseClassTypeSignature(NdComplexTypeSignature parentTypeOrNull, SignatureWrapper wrapper)
+ throws CoreException {
+ char[] identifier = wrapper.nextWord();
+ char[] fieldDescriptor;
+
+ if (parentTypeOrNull != null) {
+ fieldDescriptor = CharArrayUtils.concat(
+ parentTypeOrNull.getRawType().getFieldDescriptorWithoutTrailingSemicolon(),
+ INNER_TYPE_SEPARATOR, identifier, FIELD_DESCRIPTOR_SUFFIX);
+ } else {
+ fieldDescriptor = CharArrayUtils.concat(identifier, FIELD_DESCRIPTOR_SUFFIX);
+ }
+
+ char[] genericSignature = wrapper.signature;
+ boolean hasGenericArguments = (genericSignature.length > wrapper.start)
+ && genericSignature[wrapper.start] == '<';
+ boolean isRawTypeWithNestedClass = genericSignature[wrapper.start] == '.';
+ NdTypeId rawType = createTypeIdFromFieldDescriptor(fieldDescriptor);
+ NdTypeSignature result = rawType;
+
+ boolean checkForSemicolon = true;
+ // Special optimization for signatures with no type annotations, no arrays, and no generic arguments that
+ // are not an inner type of a class that can't use this optimization. Basically, if there would be no attributes
+ // set on a NdComplexTypeSignature besides what it picks up from its raw type, we just use the raw type.
+ if (hasGenericArguments || parentTypeOrNull != null || isRawTypeWithNestedClass) {
+ NdComplexTypeSignature typeSignature = new NdComplexTypeSignature(getNd());
+ typeSignature.setRawType(rawType);
+
+ if (hasGenericArguments) {
+ wrapper.start++;
+ while (wrapper.start < genericSignature.length && (genericSignature[wrapper.start] != '>')) {
+ NdTypeArgument typeArgument = new NdTypeArgument(getNd(), typeSignature);
+
+ switch (genericSignature[wrapper.start]) {
+ case '+': {
+ typeArgument.setWildcard(NdTypeArgument.WILDCARD_SUPER);
+ wrapper.start++;
+ break;
+ }
+ case '-': {
+ typeArgument.setWildcard(NdTypeArgument.WILDCARD_EXTENDS);
+ wrapper.start++;
+ break;
+ }
+ case '*': {
+ typeArgument.setWildcard(NdTypeArgument.WILDCARD_QUESTION);
+ wrapper.start++;
+ continue;
+ }
+ }
+
+ NdTypeSignature nextSignature = createTypeSignature(wrapper, null);
+ typeArgument.setType(nextSignature);
+ }
+
+ skipChar(wrapper, '>');
+ }
+ result = typeSignature;
+
+ if (parentTypeOrNull != null) {
+ typeSignature.setGenericDeclaringType(parentTypeOrNull);
+ }
+
+ if (genericSignature[wrapper.start] == '.') {
+ // Don't check for a semicolon if we hit this branch since the recursive call to parseClassTypeSignature
+ // will do this
+ checkForSemicolon = false;
+ // Identifiers shouldn't start with '.'
+ skipChar(wrapper, '.');
+ result = parseClassTypeSignature(typeSignature, wrapper);
+ }
+ }
+
+ if (checkForSemicolon) {
+ skipChar(wrapper, ';');
+ }
+
+ return result;
+ }
+
+ private NdTypeId createTypeIdFromFieldDescriptor(char[] typeName) {
+ if (typeName == null) {
+ return null;
+ }
+ return this.index.createTypeId(typeName);
+ }
+
+ /**
+ * Creates a method ID given a method selector, method descriptor, and binary type name
+ */
+ private NdMethodId createMethodId(char[] binaryTypeName, char[] methodSelector, char[] methodDescriptor) {
+ if (methodSelector == null || binaryTypeName == null || methodDescriptor == null) {
+ return null;
+ }
+
+ char[] methodId = JavaNames.getMethodId(binaryTypeName, methodSelector, methodDescriptor);
+ return this.index.createMethodId(methodId);
+ }
+
+ /**
+ * Creates a method ID given a method name (which is a method selector followed by a method descriptor.
+ */
+ private NdMethodId createMethodId(char[] binaryTypeName, char[] methodName) {
+ if (methodName == null || binaryTypeName == null) {
+ return null;
+ }
+
+ char[] methodId = JavaNames.getMethodId(binaryTypeName, methodName);
+ return this.index.createMethodId(methodId);
+ }
+
+ private void initTypeAnnotation(NdTypeAnnotation annotation, IBinaryTypeAnnotation next) {
+ int[] typePath = next.getTypePath();
+ if (typePath != null && typePath.length > 0) {
+ byte[] bytePath = new byte[typePath.length];
+ for (int idx = 0; idx < bytePath.length; idx++) {
+ bytePath[idx] = (byte)typePath[idx];
+ }
+ annotation.setPath(bytePath);
+ }
+ int targetType = next.getTargetType();
+ annotation.setTargetType(targetType);
+ switch (targetType) {
+ case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER:
+ case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER:
+ annotation.setTargetInfo(next.getTypeParameterIndex());
+ break;
+ case AnnotationTargetTypeConstants.CLASS_EXTENDS:
+ annotation.setTargetInfo(next.getSupertypeIndex());
+ break;
+ case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER_BOUND:
+ case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER_BOUND:
+ annotation.setTargetInfo((byte)next.getTypeParameterIndex(), (byte)next.getBoundIndex());
+ break;
+ case AnnotationTargetTypeConstants.FIELD:
+ case AnnotationTargetTypeConstants.METHOD_RETURN:
+ case AnnotationTargetTypeConstants.METHOD_RECEIVER:
+ break;
+ case AnnotationTargetTypeConstants.METHOD_FORMAL_PARAMETER :
+ annotation.setTargetInfo(next.getMethodFormalParameterIndex());
+ break;
+ case AnnotationTargetTypeConstants.THROWS :
+ annotation.setTargetInfo(next.getThrowsTypeIndex());
+ break;
+
+ default:
+ throw new IllegalStateException("Target type not handled " + targetType); //$NON-NLS-1$
+ }
+ initAnnotation(annotation, next.getAnnotation());
+ }
+
+ private void initAnnotation(NdAnnotation annotation, IBinaryAnnotation next) {
+ annotation.setType(createTypeIdFromBinaryName(next.getTypeName()));
+
+ IBinaryElementValuePair[] pairs = next.getElementValuePairs();
+
+ if (pairs != null) {
+ for (IBinaryElementValuePair element : pairs) {
+ NdAnnotationValuePair nextPair = new NdAnnotationValuePair(annotation, element.getName());
+ nextPair.setValue(createConstantFromMixedType(element.getValue()));
+ }
+ }
+ }
+
+ private void logInfo(String string) {
+ if (ENABLE_LOGGING) {
+ Package.logInfo(string);
+ }
+ }
+
+ private NdTypeId createTypeIdFromBinaryName(char[] binaryName) {
+ if (binaryName == null) {
+ return null;
+ }
+
+ return this.index.createTypeId(JavaNames.binaryNameToFieldDescriptor(binaryName));
+ }
+
+ /**
+ *
+ * @param value
+ * accepts all values returned from {@link IBinaryElementValuePair#getValue()}
+ */
+ public NdConstant createConstantFromMixedType(Object value) {
+ if (value instanceof Constant) {
+ Constant constant = (Constant) value;
+
+ return NdConstant.create(getNd(), constant);
+ } else if (value instanceof ClassSignature) {
+ ClassSignature signature = (ClassSignature) value;
+
+ char[] binaryName = JavaNames.binaryNameToFieldDescriptor(signature.getTypeName());
+ NdTypeSignature typeId = this.index.createTypeId(binaryName);
+ return NdConstantClass.create(getNd(), typeId);
+ } else if (value instanceof IBinaryAnnotation) {
+ IBinaryAnnotation binaryAnnotation = (IBinaryAnnotation) value;
+
+ NdAnnotationInConstant annotation = new NdAnnotationInConstant(getNd());
+ initAnnotation(annotation, binaryAnnotation);
+ return NdConstantAnnotation.create(getNd(), annotation);
+ } else if (value instanceof Object[]) {
+ NdConstantArray result = new NdConstantArray(getNd());
+ Object[] array = (Object[]) value;
+
+ for (Object next : array) {
+ NdConstant nextConstant = createConstantFromMixedType(next);
+ nextConstant.setParent(result);
+ }
+ return result;
+ } else if (value instanceof EnumConstantSignature) {
+ EnumConstantSignature signature = (EnumConstantSignature) value;
+
+ NdConstantEnum result = NdConstantEnum.create(createTypeIdFromBinaryName(signature.getTypeName()),
+ new String(signature.getEnumConstantName()));
+
+ return result;
+ }
+ throw new IllegalStateException("Unknown constant type " + value.getClass().getName()); //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/GenericSignatures.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/GenericSignatures.java
new file mode 100644
index 000000000..caca5a27f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/GenericSignatures.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.lookup.SignatureWrapper;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * Contains static factory methods for constructing {@link SignatureWrapper} from various types.
+ */
+public class GenericSignatures {
+ private static final char[][] EMPTY_CHAR_ARRAY_ARRAY = new char[0][];
+
+ public static SignatureWrapper getGenericSignature(IBinaryMethod next) {
+ char[] signature = next.getGenericSignature();
+ if (signature == null) {
+ signature = next.getMethodDescriptor();
+ }
+
+ return new SignatureWrapper(signature);
+ }
+
+ /**
+ * Returns the generic signature for the given field. If the field has no generic signature, one is generated
+ * from the type's field descriptor.
+ */
+ public static SignatureWrapper getGenericSignature(IBinaryType binaryType) {
+ char[][] interfaces = binaryType.getInterfaceNames();
+ if (interfaces == null) {
+ interfaces = EMPTY_CHAR_ARRAY_ARRAY;
+ }
+ char[] genericSignature = binaryType.getGenericSignature();
+ if (genericSignature == null) {
+ int startIndex = binaryType.getSuperclassName() != null ? 3 : 0;
+ char[][] toCatenate = new char[startIndex + (interfaces.length * 3)][];
+ char[] prefix = new char[]{'L'};
+ char[] suffix = new char[]{';'};
+
+ if (binaryType.getSuperclassName() != null) {
+ toCatenate[0] = prefix;
+ toCatenate[1] = binaryType.getSuperclassName();
+ toCatenate[2] = suffix;
+ }
+
+ for (int idx = 0; idx < interfaces.length; idx++) {
+ int catIndex = startIndex + idx * 3;
+ toCatenate[catIndex] = prefix;
+ toCatenate[catIndex + 1] = interfaces[idx];
+ toCatenate[catIndex + 2] = suffix;
+ }
+
+ genericSignature = CharArrayUtils.concat(toCatenate);
+ }
+
+ SignatureWrapper signatureWrapper = new SignatureWrapper(genericSignature);
+ return signatureWrapper;
+ }
+
+ /**
+ * Returns the generic signature for the given field. If the field has no generic signature, one is generated
+ * from the type's field descriptor.
+ */
+ static SignatureWrapper getGenericSignatureFor(IBinaryField nextField) {
+ char[] signature = nextField.getGenericSignature();
+ if (signature == null) {
+ signature = nextField.getTypeName();
+ }
+ return new SignatureWrapper(signature);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/HierarchicalASTVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/HierarchicalASTVisitor.java
new file mode 100644
index 000000000..66f82de0e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/HierarchicalASTVisitor.java
@@ -0,0 +1,1139 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import org.eclipse.jdt.core.dom.*;
+
+/**
+ *
+ * <p>
+ * This class provides a convenient behaviour-only extension mechanism for the ASTNode hierarchy. If
+ * you feel like you would like to add a method to the ASTNode hierarchy (or a subtree of the
+ * hierarchy), and you want to have different implementations of it at different points in the
+ * hierarchy, simply create a HierarchicalASTVisitor representing the new method and all its
+ * implementations, locating each implementation within the right visit(XX) method. If you wanted to
+ * add a method implementation to abstract class Foo, an ASTNode descendant, put your implementation
+ * in visit(Foo). This class will provide appropriate dispatch, just as if the method
+ * implementations had been added to the ASTNode hierarchy.
+ * </p>
+ *
+ * <p>
+ * <b>Details:<b>
+ * </p>
+ *
+ * <p>
+ * This class has a visit(XX node) method for every class (concrete or abstract) XX in the ASTNode
+ * hierarchy. In this class' default implementations of these methods, the method corresponding to a
+ * given ASTNode descendant class will call (and return the return value of) the visit(YY) method
+ * for it's superclass YY, with the exception of the visit(ASTNode) method which simply returns
+ * true, since ASTNode doesn't have a superclass that is within the ASTNode hierarchy.
+ * </p>
+ *
+ * <p>
+ * Because of this organization, when visit(XX) methods are overridden in a subclass, and the
+ * visitor is applied to a node, only the most specialized overridden method implementation for the
+ * node's type will be called, unless this most specialized method calls other visit methods (this
+ * is discouraged) or, (preferably) calls super.visit(XX node), (the reference type of the parameter
+ * must be XX) which will invoke this class' implementation of the method, which will, in turn,
+ * invoke the visit(YY) method corresponding to the superclass, YY.
+ * </p>
+ *
+ * <p>
+ * Thus, the dispatching behaviour achieved when HierarchicalASTVisitors' visit(XX) methods,
+ * corresponding to a particular concrete or abstract ASTNode descendant class, are overridden is
+ * exactly analogous to the dispatching behaviour obtained when method implementations are added to
+ * the same ASTNode descendant classes.
+ * </p>
+ *
+ */
+/*
+ * IMPORTANT NOTE:
+ *
+ * The structure and behaviour of this class is
+ * verified reflectively by
+ * org.eclipse.jdt.ui.tests.core.HierarchicalASTVisitorTest
+ *
+ */
+public abstract class HierarchicalASTVisitor extends ASTVisitor {
+//TODO: check callers for handling of comments
+
+//---- Begin ASTNode Hierarchy -------------------------------------
+
+ /**
+ * Visits the given AST node.
+ * <p>
+ * The default implementation does nothing and return true. Subclasses may reimplement.
+ * </p>
+ *
+ * @param node the node to visit
+ * @return <code>true</code> if the children of this node should be visited, and
+ * <code>false</code> if the children of this node should be skipped
+ */
+ public boolean visit(ASTNode node) {
+ return true;
+ }
+
+ /**
+ * End of visit the given AST node.
+ * <p>
+ * The default implementation does nothing. Subclasses may reimplement.
+ * </p>
+ *
+ * @param node the node to visit
+ */
+ public void endVisit(ASTNode node) {
+ // do nothing
+ }
+
+ @Override
+ public boolean visit(AnonymousClassDeclaration node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(AnonymousClassDeclaration node) {
+ endVisit((ASTNode)node);
+ }
+
+//---- Begin BodyDeclaration Hierarchy ---------------------------
+ public boolean visit(BodyDeclaration node) {
+ return visit((ASTNode)node);
+ }
+
+ public void endVisit(BodyDeclaration node) {
+ endVisit((ASTNode)node);
+ }
+
+ //---- Begin AbstractTypeDeclaration Hierarchy ---------------------------
+ public boolean visit(AbstractTypeDeclaration node) {
+ return visit((BodyDeclaration)node);
+ }
+
+ public void endVisit(AbstractTypeDeclaration node) {
+ endVisit((BodyDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(AnnotationTypeDeclaration node) {
+ return visit((AbstractTypeDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(AnnotationTypeDeclaration node) {
+ endVisit((AbstractTypeDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(EnumDeclaration node) {
+ return visit((AbstractTypeDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(EnumDeclaration node) {
+ endVisit((AbstractTypeDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration node) {
+ return visit((AbstractTypeDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(TypeDeclaration node) {
+ endVisit((AbstractTypeDeclaration)node);
+ }
+
+ //---- End AbstractTypeDeclaration Hierarchy ---------------------------
+
+ @Override
+ public boolean visit(AnnotationTypeMemberDeclaration node) {
+ return visit((BodyDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(AnnotationTypeMemberDeclaration node) {
+ endVisit((BodyDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(EnumConstantDeclaration node) {
+ return visit((BodyDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(EnumConstantDeclaration node) {
+ endVisit((BodyDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(FieldDeclaration node) {
+ return visit((BodyDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(FieldDeclaration node) {
+ endVisit((BodyDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(Initializer node) {
+ return visit((BodyDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(Initializer node) {
+ endVisit((BodyDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ return visit((BodyDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(MethodDeclaration node) {
+ endVisit((BodyDeclaration)node);
+ }
+
+//---- End BodyDeclaration Hierarchy -----------------------------
+
+ @Override
+ public boolean visit(CatchClause node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(CatchClause node) {
+ endVisit((ASTNode)node);
+ }
+
+//---- Begin Comment Hierarchy ----------------------------------
+ public boolean visit(Comment node) {
+ return visit((ASTNode)node);
+ }
+
+ public void endVisit(Comment node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(BlockComment node) {
+ return visit((Comment)node);
+ }
+
+ @Override
+ public void endVisit(BlockComment node) {
+ endVisit((Comment)node);
+ }
+
+ @Override
+ public boolean visit(Javadoc node) {
+ return visit((Comment)node);
+ }
+
+ @Override
+ public void endVisit(Javadoc node) {
+ endVisit((Comment)node);
+ }
+
+ @Override
+ public boolean visit(LineComment node) {
+ return visit((Comment)node);
+ }
+
+ @Override
+ public void endVisit(LineComment node) {
+ endVisit((Comment)node);
+ }
+
+//---- End Comment Hierarchy -----------------------------
+
+ @Override
+ public boolean visit(CompilationUnit node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(CompilationUnit node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(Dimension node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(Dimension node) {
+ endVisit((ASTNode)node);
+ }
+
+//---- Begin Expression Hierarchy ----------------------------------
+ public boolean visit(Expression node) {
+ return visit((ASTNode)node);
+ }
+
+ public void endVisit(Expression node) {
+ endVisit((ASTNode)node);
+ }
+
+ //---- Begin Annotation Hierarchy ----------------------------------
+ public boolean visit(Annotation node) {
+ return visit((Expression)node);
+ }
+
+ public void endVisit(Annotation node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(MarkerAnnotation node) {
+ return visit((Annotation)node);
+ }
+
+ @Override
+ public void endVisit(MarkerAnnotation node) {
+ endVisit((Annotation)node);
+ }
+
+ @Override
+ public boolean visit(NormalAnnotation node) {
+ return visit((Annotation)node);
+ }
+
+ @Override
+ public void endVisit(NormalAnnotation node) {
+ endVisit((Annotation)node);
+ }
+
+ @Override
+ public boolean visit(SingleMemberAnnotation node) {
+ return visit((Annotation)node);
+ }
+
+ @Override
+ public void endVisit(SingleMemberAnnotation node) {
+ endVisit((Annotation)node);
+ }
+
+ //---- End Annotation Hierarchy -----------------------------
+
+ @Override
+ public boolean visit(ArrayAccess node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(ArrayAccess node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(ArrayCreation node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(ArrayCreation node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(ArrayInitializer node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(ArrayInitializer node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(Assignment node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(Assignment node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(BooleanLiteral node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(BooleanLiteral node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(CastExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(CastExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(CharacterLiteral node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(CharacterLiteral node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(ClassInstanceCreation node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(ClassInstanceCreation node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(ConditionalExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(ConditionalExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(FieldAccess node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(FieldAccess node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(InfixExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(InfixExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(InstanceofExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(InstanceofExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(LambdaExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(LambdaExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(MethodInvocation node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(MethodInvocation node) {
+ endVisit((Expression)node);
+ }
+
+ //---- Begin MethodReference Hierarchy ----------------------------------
+ public boolean visit(MethodReference node) {
+ return visit((Expression)node);
+ }
+
+ public void endVisit(MethodReference node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(CreationReference node) {
+ return visit((MethodReference)node);
+ }
+
+ @Override
+ public void endVisit(CreationReference node) {
+ endVisit((MethodReference)node);
+ }
+
+ @Override
+ public boolean visit(ExpressionMethodReference node) {
+ return visit((MethodReference)node);
+ }
+
+ @Override
+ public void endVisit(ExpressionMethodReference node) {
+ endVisit((MethodReference)node);
+ }
+
+ @Override
+ public boolean visit(SuperMethodReference node) {
+ return visit((MethodReference)node);
+ }
+
+ @Override
+ public void endVisit(SuperMethodReference node) {
+ endVisit((MethodReference)node);
+ }
+
+ @Override
+ public boolean visit(TypeMethodReference node) {
+ return visit((MethodReference)node);
+ }
+
+ @Override
+ public void endVisit(TypeMethodReference node) {
+ endVisit((MethodReference)node);
+ }
+
+ //---- End MethodReference Hierarchy ------------------------------------
+
+ //---- Begin Name Hierarchy ----------------------------------
+ public boolean visit(Name node) {
+ return visit((Expression)node);
+ }
+
+ public void endVisit(Name node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(QualifiedName node) {
+ return visit((Name)node);
+ }
+
+ @Override
+ public void endVisit(QualifiedName node) {
+ endVisit((Name)node);
+ }
+
+ @Override
+ public boolean visit(SimpleName node) {
+ return visit((Name)node);
+ }
+
+ @Override
+ public void endVisit(SimpleName node) {
+ endVisit((Name)node);
+ }
+
+ //---- End Name Hierarchy ------------------------------------
+
+ @Override
+ public boolean visit(NullLiteral node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(NullLiteral node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(NumberLiteral node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(NumberLiteral node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(ParenthesizedExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(ParenthesizedExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(PostfixExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(PostfixExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(PrefixExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(PrefixExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(StringLiteral node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(StringLiteral node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(SuperFieldAccess node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(SuperFieldAccess node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(SuperMethodInvocation node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(SuperMethodInvocation node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(ThisExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(ThisExpression node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(TypeLiteral node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(TypeLiteral node) {
+ endVisit((Expression)node);
+ }
+
+ @Override
+ public boolean visit(VariableDeclarationExpression node) {
+ return visit((Expression)node);
+ }
+
+ @Override
+ public void endVisit(VariableDeclarationExpression node) {
+ endVisit((Expression)node);
+ }
+
+ //---- End Expression Hierarchy ----------------------------------
+
+ @Override
+ public boolean visit(ImportDeclaration node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(ImportDeclaration node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(MemberRef node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(MemberRef node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(MemberValuePair node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(MemberValuePair node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(MethodRef node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(MethodRef node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(MethodRefParameter node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(MethodRefParameter node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(Modifier node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(Modifier node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(PackageDeclaration node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(PackageDeclaration node) {
+ endVisit((ASTNode)node);
+ }
+
+//---- Begin Statement Hierarchy ---------------------------------
+ public boolean visit(Statement node) {
+ return visit((ASTNode)node);
+ }
+
+ public void endVisit(Statement node) {
+ endVisit((ASTNode)node);
+ }
+
+
+ @Override
+ public boolean visit(AssertStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(AssertStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(Block node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(Block node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(BreakStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(BreakStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(ConstructorInvocation node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(ConstructorInvocation node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(ContinueStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(ContinueStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(DoStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(DoStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(EmptyStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(EmptyStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(EnhancedForStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(EnhancedForStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(ExpressionStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(ExpressionStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(ForStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(ForStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(IfStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(IfStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(LabeledStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(LabeledStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(ReturnStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(ReturnStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(SuperConstructorInvocation node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(SuperConstructorInvocation node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(SwitchCase node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(SwitchCase node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(SwitchStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(SwitchStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(SynchronizedStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(SynchronizedStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(ThrowStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(ThrowStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(TryStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(TryStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(TypeDeclarationStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(TypeDeclarationStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(VariableDeclarationStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(VariableDeclarationStatement node) {
+ endVisit((Statement)node);
+ }
+
+ @Override
+ public boolean visit(WhileStatement node) {
+ return visit((Statement)node);
+ }
+
+ @Override
+ public void endVisit(WhileStatement node) {
+ endVisit((Statement)node);
+ }
+
+//---- End Statement Hierarchy ----------------------------------
+
+ @Override
+ public boolean visit(TagElement node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(TagElement node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(TextElement node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(TextElement node) {
+ endVisit((ASTNode)node);
+ }
+
+
+//---- Begin Type Hierarchy --------------------------------------
+ public boolean visit(Type node) {
+ return visit((ASTNode)node);
+ }
+
+ public void endVisit(Type node) {
+ endVisit((ASTNode)node);
+ }
+
+//---- Begin Annotatable Type Hierarchy --------------------------------------
+ public boolean visit(AnnotatableType node) {
+ return visit((Type)node);
+ }
+
+ public void endVisit(AnnotatableType node) {
+ endVisit((Type)node);
+ }
+
+ @Override
+ public boolean visit(NameQualifiedType node) {
+ return visit((AnnotatableType)node);
+ }
+
+ @Override
+ public void endVisit(NameQualifiedType node) {
+ endVisit((AnnotatableType)node);
+ }
+
+ @Override
+ public boolean visit(PrimitiveType node) {
+ return visit((AnnotatableType)node);
+ }
+
+ @Override
+ public void endVisit(PrimitiveType node) {
+ endVisit((AnnotatableType)node);
+ }
+
+ @Override
+ public boolean visit(QualifiedType node) {
+ return visit((AnnotatableType)node);
+ }
+
+ @Override
+ public void endVisit(QualifiedType node) {
+ endVisit((AnnotatableType)node);
+ }
+
+ @Override
+ public boolean visit(SimpleType node) {
+ return visit((AnnotatableType)node);
+ }
+
+ @Override
+ public void endVisit(SimpleType node) {
+ endVisit((AnnotatableType)node);
+ }
+
+ @Override
+ public boolean visit(WildcardType node) {
+ return visit((AnnotatableType)node);
+ }
+
+ @Override
+ public void endVisit(WildcardType node) {
+ endVisit((AnnotatableType)node);
+ }
+//---- End Annotatable Type Hierarchy --------------------------------------
+
+ @Override
+ public boolean visit(ArrayType node) {
+ return visit((Type)node);
+ }
+
+ @Override
+ public void endVisit(ArrayType node) {
+ endVisit((Type)node);
+ }
+
+ @Override
+ public boolean visit(IntersectionType node) {
+ return visit((Type)node);
+ }
+
+ @Override
+ public void endVisit(IntersectionType node) {
+ endVisit((Type)node);
+ }
+
+ @Override
+ public boolean visit(ParameterizedType node) {
+ return visit((Type)node);
+ }
+
+ @Override
+ public void endVisit(ParameterizedType node) {
+ endVisit((Type)node);
+ }
+
+ @Override
+ public boolean visit(UnionType node) {
+ return visit((Type)node);
+ }
+
+ @Override
+ public void endVisit(UnionType node) {
+ endVisit((Type)node);
+ }
+
+//---- End Type Hierarchy ----------------------------------------
+
+ @Override
+ public boolean visit(TypeParameter node) {
+ return visit((ASTNode)node);
+ }
+
+ @Override
+ public void endVisit(TypeParameter node) {
+ endVisit((ASTNode)node);
+ }
+
+
+//---- Begin VariableDeclaration Hierarchy ---------------------------
+ public boolean visit(VariableDeclaration node) {
+ return visit((ASTNode)node);
+ }
+
+ public void endVisit(VariableDeclaration node) {
+ endVisit((ASTNode)node);
+ }
+
+ @Override
+ public boolean visit(SingleVariableDeclaration node) {
+ return visit((VariableDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(SingleVariableDeclaration node) {
+ endVisit((VariableDeclaration)node);
+ }
+
+ @Override
+ public boolean visit(VariableDeclarationFragment node) {
+ return visit((VariableDeclaration)node);
+ }
+
+ @Override
+ public void endVisit(VariableDeclarationFragment node) {
+ endVisit((VariableDeclaration)node);
+ }
+
+//---- End VariableDeclaration Hierarchy -----------------------------
+//---- End ASTNode Hierarchy -----------------------------------------
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexTester.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexTester.java
new file mode 100644
index 000000000..eba1d711b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexTester.java
@@ -0,0 +1,454 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+import org.eclipse.jdt.internal.compiler.env.ClassSignature;
+import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
+import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+public class IndexTester {
+
+ private static final class TypeAnnotationWrapper {
+ private IBinaryTypeAnnotation annotation;
+
+ public TypeAnnotationWrapper(IBinaryTypeAnnotation next) {
+ this.annotation = next;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode;
+ int[] typePath = this.annotation.getTypePath();
+
+ hashCode = Arrays.hashCode(typePath);
+ hashCode = hashCode * 31 + this.annotation.getTargetType();
+ hashCode = hashCode * 31 + this.annotation.getTypeParameterIndex();
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ return this.annotation.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj.getClass() != TypeAnnotationWrapper.class) {
+ return false;
+ }
+
+ TypeAnnotationWrapper wrapper = (TypeAnnotationWrapper) obj;
+ IBinaryTypeAnnotation otherAnnotation = wrapper.annotation;
+
+ int[] typePath = this.annotation.getTypePath();
+ int[] otherTypePath = otherAnnotation.getTypePath();
+
+ if (!Arrays.equals(typePath, otherTypePath)) {
+ return false;
+ }
+
+ if (this.annotation.getTargetType() != otherAnnotation.getTargetType()) {
+ return false;
+ }
+
+ if (this.annotation.getBoundIndex() != otherAnnotation.getBoundIndex()) {
+ return false;
+ }
+
+ if (this.annotation.getMethodFormalParameterIndex() != otherAnnotation.getMethodFormalParameterIndex()) {
+ return false;
+ }
+
+ if (this.annotation.getSupertypeIndex() != otherAnnotation.getSupertypeIndex()) {
+ return false;
+ }
+
+ if (this.annotation.getThrowsTypeIndex() != otherAnnotation.getThrowsTypeIndex()) {
+ return false;
+ }
+
+ if (this.annotation.getTypeParameterIndex() != otherAnnotation.getTypeParameterIndex()) {
+ return false;
+ }
+
+ return IndexTester.isEqual(this.annotation.getAnnotation(), otherAnnotation.getAnnotation());
+ }
+ }
+
+ public static void testType(IBinaryType expected, IBinaryType actual) {
+ String contextPrefix = safeString(actual.getName());
+
+ IBinaryTypeAnnotation[] expectedTypeAnnotations = expected.getTypeAnnotations();
+ IBinaryTypeAnnotation[] actualTypeAnnotations = actual.getTypeAnnotations();
+
+ compareTypeAnnotations(contextPrefix, expectedTypeAnnotations, actualTypeAnnotations);
+
+ IBinaryAnnotation[] expectedBinaryAnnotations = expected.getAnnotations();
+ IBinaryAnnotation[] actualBinaryAnnotations = actual.getAnnotations();
+
+ compareAnnotations(contextPrefix, expectedBinaryAnnotations, actualBinaryAnnotations);
+
+ compareGenericSignatures(contextPrefix + ": The generic signature did not match", //$NON-NLS-1$
+ expected.getGenericSignature(), actual.getGenericSignature());
+
+ assertEquals(contextPrefix + ": The enclosing method name did not match", expected.getEnclosingMethod(), //$NON-NLS-1$
+ actual.getEnclosingMethod());
+ assertEquals(contextPrefix + ": The enclosing method name did not match", expected.getEnclosingTypeName(), //$NON-NLS-1$
+ actual.getEnclosingTypeName());
+
+ IBinaryField[] expectedFields = expected.getFields();
+ IBinaryField[] actualFields = actual.getFields();
+
+ if (expectedFields != actualFields) {
+ if (expectedFields == null && actualFields != null) {
+ throw new IllegalStateException(contextPrefix + "Expected fields was null -- actual fields were not"); //$NON-NLS-1$
+ }
+ if (expectedFields.length != actualFields.length) {
+ throw new IllegalStateException(
+ contextPrefix + "The expected and actual number of fields did not match"); //$NON-NLS-1$
+ }
+
+ for (int fieldIdx = 0; fieldIdx < actualFields.length; fieldIdx++) {
+ compareFields(contextPrefix, expectedFields[fieldIdx], actualFields[fieldIdx]);
+ }
+ }
+
+ // Commented this out because the "expected" values often appear to be invalid paths when the "actual"
+ // ones are correct.
+ assertEquals("The file name did not match", expected.getFileName(), actual.getFileName()); //$NON-NLS-1$
+ assertEquals("The interface names did not match", expected.getInterfaceNames(), actual.getInterfaceNames()); //$NON-NLS-1$
+
+ // Member types are not expected to match during indexing since the index uses discovered cross-references,
+ // not the member types encoded in the .class file.
+ // expected.getMemberTypes() != actual.getMemberTypes()
+
+ IBinaryMethod[] expectedMethods = expected.getMethods();
+ IBinaryMethod[] actualMethods = actual.getMethods();
+
+ if (expectedMethods != actualMethods) {
+ if (expectedMethods == null || actualMethods == null) {
+ throw new IllegalStateException("One of the method arrays was null"); //$NON-NLS-1$
+ }
+
+ if (expectedMethods.length != actualMethods.length) {
+ throw new IllegalStateException("The number of methods didn't match"); //$NON-NLS-1$
+ }
+
+ for (int i = 0; i < actualMethods.length; i++) {
+ IBinaryMethod actualMethod = actualMethods[i];
+ IBinaryMethod expectedMethod = expectedMethods[i];
+
+ compareMethods(contextPrefix, expectedMethod, actualMethod);
+ }
+ }
+
+ assertEquals("The missing type names did not match", expected.getMissingTypeNames(), //$NON-NLS-1$
+ actual.getMissingTypeNames());
+ assertEquals("The modifiers don't match", expected.getModifiers(), actual.getModifiers()); //$NON-NLS-1$
+ assertEquals("The names don't match.", expected.getName(), actual.getName()); //$NON-NLS-1$
+ assertEquals("The source name doesn't match", expected.getSourceName(), actual.getSourceName()); //$NON-NLS-1$
+ assertEquals("The superclass name doesn't match", expected.getSuperclassName(), actual.getSuperclassName()); //$NON-NLS-1$
+ assertEquals("The tag bits don't match.", expected.getTagBits(), actual.getTagBits()); //$NON-NLS-1$
+
+ compareTypeAnnotations(contextPrefix, expected.getTypeAnnotations(), actual.getTypeAnnotations());
+ }
+
+ private static <T> void assertEquals(String message, T o1, T o2) {
+ if (!isEqual(o1, o2)) {
+ throw new IllegalStateException(message + ": expected = " + getString(o1) + ", actual = " + getString(o2)); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+
+ private static String getString(Object object) {
+ if (object instanceof char[]) {
+ char[] charArray = (char[]) object;
+
+ return new String(charArray);
+ }
+ return object.toString();
+ }
+
+ static <T> boolean isEqual(T o1, T o2) {
+ if (o1 == o2) {
+ return true;
+ }
+
+ if (o1 == null || o2 == null) {
+ return false;
+ }
+
+ if (o1 instanceof ClassSignature) {
+ if (!(o2 instanceof ClassSignature)) {
+ return false;
+ }
+
+ ClassSignature sig1 = (ClassSignature) o1;
+ ClassSignature sig2 = (ClassSignature) o2;
+
+ return Arrays.equals(sig1.getTypeName(), sig2.getTypeName());
+ }
+
+ if (o1 instanceof IBinaryAnnotation) {
+ IBinaryAnnotation binaryAnnotation = (IBinaryAnnotation) o1;
+ IBinaryAnnotation otherBinaryAnnotation = (IBinaryAnnotation) o2;
+ IBinaryElementValuePair[] elementValuePairs = binaryAnnotation.getElementValuePairs();
+ IBinaryElementValuePair[] otherElementValuePairs = otherBinaryAnnotation.getElementValuePairs();
+
+ if (elementValuePairs.length != otherElementValuePairs.length) {
+ return false;
+ }
+
+ for (int idx = 0; idx < elementValuePairs.length; idx++) {
+ IBinaryElementValuePair next = elementValuePairs[idx];
+ IBinaryElementValuePair otherNext = otherElementValuePairs[idx];
+
+ char[] nextName = next.getName();
+ char[] otherNextName = otherNext.getName();
+
+ if (!Arrays.equals(nextName, otherNextName)) {
+ return false;
+ }
+
+ if (!isEqual(next.getValue(), otherNext.getValue())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (o1 instanceof IBinaryTypeAnnotation) {
+ IBinaryTypeAnnotation binaryAnnotation = (IBinaryTypeAnnotation)o1;
+ IBinaryTypeAnnotation otherBinaryAnnotation = (IBinaryTypeAnnotation)o2;
+
+ return new TypeAnnotationWrapper(binaryAnnotation).equals(new TypeAnnotationWrapper(otherBinaryAnnotation));
+ }
+
+ if (o1 instanceof Constant) {
+ if (!(o2 instanceof Constant)) {
+ return false;
+ }
+
+ if (o1 instanceof DoubleConstant && o2 instanceof DoubleConstant) {
+ DoubleConstant d1 = (DoubleConstant) o1;
+ DoubleConstant d2 = (DoubleConstant) o2;
+
+ if (Double.isNaN(d1.doubleValue()) && Double.isNaN(d2.doubleValue())) {
+ return true;
+ }
+ }
+
+ if (o1 instanceof FloatConstant && o2 instanceof FloatConstant) {
+ FloatConstant d1 = (FloatConstant) o1;
+ FloatConstant d2 = (FloatConstant) o2;
+
+ if (Float.isNaN(d1.floatValue()) && Float.isNaN(d2.floatValue())) {
+ return true;
+ }
+ }
+
+ Constant const1 = (Constant) o1;
+ Constant const2 = (Constant) o2;
+
+ return const1.hasSameValue(const2);
+ }
+
+ if (o1 instanceof EnumConstantSignature) {
+ if (!(o2 instanceof EnumConstantSignature)) {
+ return false;
+ }
+
+ EnumConstantSignature enum1 = (EnumConstantSignature) o1;
+ EnumConstantSignature enum2 = (EnumConstantSignature) o2;
+
+ return Arrays.equals(enum1.getEnumConstantName(), enum2.getEnumConstantName())
+ && Arrays.equals(enum1.getTypeName(), enum2.getTypeName());
+ }
+
+ if (o1 instanceof char[]) {
+ char[] c1 = (char[]) o1;
+ char[] c2 = (char[]) o2;
+
+ return CharArrayUtils.equals(c1, c2);
+ }
+
+ if (o1 instanceof char[][]) {
+ char[][] c1 = (char[][]) o1;
+ char[][] c2 = (char[][]) o2;
+
+ return CharArrayUtils.equals(c1, c2);
+ }
+
+ if (o1 instanceof char[][][]) {
+ char[][][] c1 = (char[][][]) o1;
+ char[][][] c2 = (char[][][]) o2;
+
+ if (c1.length != c2.length) {
+ return false;
+ }
+
+ for (int i = 0; i < c1.length; i++) {
+ if (!isEqual(c1[i], c2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (o1 instanceof Object[]) {
+ Object[] a1 = (Object[]) o1;
+ Object[] a2 = (Object[]) o2;
+
+ if (a1.length != a2.length) {
+ return false;
+ }
+
+ for (int idx = 0; idx < a1.length; idx++) {
+ if (!isEqual(a1[idx], a2[idx])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return Objects.equals(o1, o2);
+ }
+
+ private static void compareMethods(String contextPrefix, IBinaryMethod expectedMethod, IBinaryMethod actualMethod) {
+ contextPrefix = contextPrefix + "." + safeString(expectedMethod.getSelector()); //$NON-NLS-1$
+ compareAnnotations(contextPrefix, expectedMethod.getAnnotations(), actualMethod.getAnnotations());
+
+ assertEquals(contextPrefix + ": The argument names didn't match.", expectedMethod.getArgumentNames(), //$NON-NLS-1$
+ actualMethod.getArgumentNames());
+
+ assertEquals(contextPrefix + ": The default values didn't match.", expectedMethod.getDefaultValue(), //$NON-NLS-1$
+ actualMethod.getDefaultValue());
+
+ assertEquals(contextPrefix + ": The exception type names did not match.", //$NON-NLS-1$
+ expectedMethod.getExceptionTypeNames(), actualMethod.getExceptionTypeNames());
+
+ compareGenericSignatures(contextPrefix + ": The method's generic signature did not match", //$NON-NLS-1$
+ expectedMethod.getGenericSignature(), actualMethod.getGenericSignature());
+
+ assertEquals(contextPrefix + ": The method descriptors did not match.", expectedMethod.getMethodDescriptor(), //$NON-NLS-1$
+ actualMethod.getMethodDescriptor());
+ assertEquals(contextPrefix + ": The modifiers didn't match.", expectedMethod.getModifiers(), //$NON-NLS-1$
+ actualMethod.getModifiers());
+
+ char[] classFileName = "".toCharArray(); //$NON-NLS-1$
+ int minAnnotatedParameters = Math.min(expectedMethod.getAnnotatedParametersCount(),
+ actualMethod.getAnnotatedParametersCount());
+ for (int idx = 0; idx < minAnnotatedParameters; idx++) {
+ compareAnnotations(contextPrefix, expectedMethod.getParameterAnnotations(idx, classFileName),
+ actualMethod.getParameterAnnotations(idx, classFileName));
+ }
+ for (int idx = minAnnotatedParameters; idx < expectedMethod.getAnnotatedParametersCount(); idx++) {
+ compareAnnotations(contextPrefix, new IBinaryAnnotation[0],
+ expectedMethod.getParameterAnnotations(idx, classFileName));
+ }
+ for (int idx = minAnnotatedParameters; idx < actualMethod.getAnnotatedParametersCount(); idx++) {
+ compareAnnotations(contextPrefix, new IBinaryAnnotation[0],
+ actualMethod.getParameterAnnotations(idx, classFileName));
+ }
+
+ assertEquals(contextPrefix + ": The selectors did not match", expectedMethod.getSelector(), //$NON-NLS-1$
+ actualMethod.getSelector());
+ assertEquals(contextPrefix + ": The tag bits did not match", expectedMethod.getTagBits(), //$NON-NLS-1$
+ actualMethod.getTagBits());
+
+ compareTypeAnnotations(contextPrefix, expectedMethod.getTypeAnnotations(), actualMethod.getTypeAnnotations());
+ }
+
+ /**
+ * The index always provides complete generic signatures whereas some or all of the generic signature is optional
+ * for class files, so the generic signatures are expected to differ in certain situations.
+ */
+ private static void compareGenericSignatures(String message, char[] expected, char[] actual) {
+ assertEquals(message, expected, actual);
+ }
+
+ private static void compareTypeAnnotations(String contextPrefix, IBinaryTypeAnnotation[] expectedTypeAnnotations,
+ IBinaryTypeAnnotation[] actualTypeAnnotations) {
+ if (expectedTypeAnnotations == null) {
+ if (actualTypeAnnotations != null) {
+ throw new IllegalStateException(contextPrefix + ": Expected null for the annotation list but found: " //$NON-NLS-1$
+ + actualTypeAnnotations.toString());
+ }
+ return;
+ }
+
+ assertEquals(contextPrefix + ": The expected and actual number of type annotations did not match", //$NON-NLS-1$
+ expectedTypeAnnotations.length, actualTypeAnnotations.length);
+
+ for (int idx = 0; idx < expectedTypeAnnotations.length; idx++) {
+ assertEquals(contextPrefix + ": Type annotation number " + idx + " did not match", //$NON-NLS-1$//$NON-NLS-2$
+ expectedTypeAnnotations[idx], actualTypeAnnotations[idx]);
+ }
+ }
+
+ private static void compareAnnotations(String contextPrefix, IBinaryAnnotation[] expectedBinaryAnnotations,
+ IBinaryAnnotation[] actualBinaryAnnotations) {
+ if (expectedBinaryAnnotations == null || expectedBinaryAnnotations.length == 0) {
+ if (actualBinaryAnnotations != null && actualBinaryAnnotations.length != 0) {
+ throw new IllegalStateException(contextPrefix + ": Expected null for the binary annotations"); //$NON-NLS-1$
+ } else {
+ return;
+ }
+ }
+ if (actualBinaryAnnotations == null) {
+ throw new IllegalStateException(contextPrefix + ": Actual null for the binary annotations"); //$NON-NLS-1$
+ }
+ if (expectedBinaryAnnotations.length != actualBinaryAnnotations.length) {
+ throw new IllegalStateException(
+ contextPrefix + ": The expected and actual number of annotations differed. Expected: " //$NON-NLS-1$
+ + expectedBinaryAnnotations.length + ", actual: " + actualBinaryAnnotations.length); //$NON-NLS-1$
+ }
+
+ for (int idx = 0; idx < expectedBinaryAnnotations.length; idx++) {
+ if (!isEqual(expectedBinaryAnnotations[idx], actualBinaryAnnotations[idx])) {
+ throw new IllegalStateException(contextPrefix + ": An annotation had an unexpected value"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ private static void compareFields(String contextPrefix, IBinaryField field1, IBinaryField field2) {
+ contextPrefix = contextPrefix + "." + safeString(field1.getName()); //$NON-NLS-1$
+ compareAnnotations(contextPrefix, field1.getAnnotations(), field2.getAnnotations());
+ assertEquals(contextPrefix + ": Constants not equal", field1.getConstant(), field2.getConstant()); //$NON-NLS-1$
+ compareGenericSignatures(contextPrefix + ": The generic signature did not match", field1.getGenericSignature(), //$NON-NLS-1$
+ field2.getGenericSignature());
+ assertEquals(contextPrefix + ": The modifiers did not match", field1.getModifiers(), field2.getModifiers()); //$NON-NLS-1$
+ assertEquals(contextPrefix + ": The tag bits did not match", field1.getTagBits(), field2.getTagBits()); //$NON-NLS-1$
+ assertEquals(contextPrefix + ": The names did not match", field1.getName(), field2.getName()); //$NON-NLS-1$
+
+ compareTypeAnnotations(contextPrefix, field1.getTypeAnnotations(), field2.getTypeAnnotations());
+ assertEquals(contextPrefix + ": The type names did not match", field1.getTypeName(), field2.getTypeName()); //$NON-NLS-1$
+ }
+
+ private static String safeString(char[] name) {
+ if (name == null) {
+ return "<unnamed>"; //$NON-NLS-1$
+ }
+ return new String(name);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
new file mode 100644
index 000000000..b8f497899
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Indexer.java
@@ -0,0 +1,1069 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.ICoreRunnable;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaElementDelta;
+import org.eclipse.jdt.core.IJavaModelStatusConstants;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IParent;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.IDependent;
+import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
+import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
+import org.eclipse.jdt.internal.core.JavaElementDelta;
+import org.eclipse.jdt.internal.core.JavaModel;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.java.FileFingerprint;
+import org.eclipse.jdt.internal.core.nd.java.FileFingerprint.FingerprintTestResult;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
+import org.eclipse.jdt.internal.core.nd.java.NdBinding;
+import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
+import org.eclipse.jdt.internal.core.nd.java.NdWorkspaceLocation;
+import org.eclipse.jdt.internal.core.nd.java.TypeRef;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
+import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
+import org.eclipse.jdt.internal.core.nd.java.model.IndexBinaryType;
+import org.eclipse.jdt.internal.core.search.processing.IJob;
+
+public final class Indexer {
+ private Nd nd;
+ private IWorkspaceRoot root;
+
+ private static Indexer indexer;
+ public static boolean DEBUG;
+ public static boolean DEBUG_ALLOCATIONS;
+ public static boolean DEBUG_TIMING;
+ public static boolean DEBUG_INSERTIONS;
+ public static boolean DEBUG_SELFTEST;
+
+ /**
+ * True iff automatic reindexing (that is, the {@link #rescanAll()} method) is disabled
+ * Synchronize on {@link #automaticIndexingMutex} while accessing.
+ */
+ private boolean enableAutomaticIndexing = true;
+ /**
+ * True iff any code tried to schedule reindexing while automatic reindexing was disabled.
+ * Synchronize on {@link #automaticIndexingMutex} while accessing.
+ */
+ private boolean indexerDirtiedWhileDisabled = false;
+ private final Object automaticIndexingMutex = new Object();
+
+ /**
+ * Enable this to index the content of output folders, in cases where that content exists and
+ * is up-to-date. This is much faster than indexing source files directly.
+ */
+ public static boolean EXPERIMENTAL_INDEX_OUTPUT_FOLDERS;
+ private static final Object mutex = new Object();
+ private static final long MS_TO_NS = 1000000;
+
+ private Object listenersMutex = new Object();
+ /**
+ * Listener list. Copy-on-write. Synchronize on "listenersMutex" before accessing.
+ */
+ private Set<Listener> listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>());
+
+ private Job rescanJob = Job.create(Messages.Indexer_updating_index_job_name, new ICoreRunnable() {
+ @Override
+ public void run(IProgressMonitor monitor) throws CoreException {
+ rescan(monitor);
+ }
+ });
+
+ public static interface Listener {
+ void consume(IndexerEvent event);
+ }
+
+ public static Indexer getInstance() {
+ synchronized (mutex) {
+ if (indexer == null) {
+ indexer = new Indexer(JavaIndex.getGlobalNd(), ResourcesPlugin.getWorkspace().getRoot());
+ }
+ return indexer;
+ }
+ }
+
+ /**
+ * Enables or disables the "rescanAll" method. When set to false, rescanAll does nothing
+ * and indexing will only be triggered when invoking {@link #waitForIndex}.
+ * <p>
+ * Normally the indexer runs automatically and asynchronously when resource changes occur.
+ * However, if this variable is set to false the indexer only runs when someone invokes
+ * {@link #waitForIndex(IProgressMonitor)}. This can be used to eliminate race conditions
+ * when running the unit tests, since indexing will not occur unless it is triggered
+ * explicitly.
+ * <p>
+ * Synchronize on {@link #automaticIndexingMutex} before accessing.
+ */
+ public void enableAutomaticIndexing(boolean enabled) {
+ boolean runRescan = false;
+ synchronized (this.automaticIndexingMutex) {
+ if (this.enableAutomaticIndexing == enabled) {
+ return;
+ }
+ this.enableAutomaticIndexing = enabled;
+ if (enabled && this.indexerDirtiedWhileDisabled) {
+ runRescan = true;
+ }
+ }
+
+ if (runRescan) {
+ // Force a rescan when re-enabling automatic indexing since we may have missed an update
+ this.rescanJob.schedule();
+ }
+
+ if (!enabled) {
+ // Wait for any existing indexing operations to finish when disabling automatic indexing since
+ // we only want explicitly-triggered indexing operations to run after the method returns
+ try {
+ this.rescanJob.join(0, null);
+ } catch (OperationCanceledException | InterruptedException e) {
+ // Don't care
+ }
+ }
+ }
+
+ /**
+ * Amount of time (milliseconds) unreferenced files are allowed to sit in the index before they are discarded.
+ * Making this too short will cause some operations (classpath modifications, closing/reopening projects, etc.)
+ * to become more expensive. Making this too long will waste space in the database.
+ * <p>
+ * The value of this is stored in the JDT core preference called "garbageCleanupTimeoutMs". The default value
+ * is 3 days.
+ */
+ private static long getGarbageCleanupTimeout() {
+ return Platform.getPreferencesService().getLong(JavaCore.PLUGIN_ID, "garbageCleanupTimeoutMs", //$NON-NLS-1$
+ 1000 * 60 * 60 * 24 * 3,
+ null);
+ }
+
+ /**
+ * Amount of time (milliseconds) before we update the "used" timestamp on a file in the index. We don't update
+ * the timestamps every update since doing so would be unnecessarily inefficient... but if any of the timestamps
+ * is older than this update period, we refresh it.
+ */
+ private static long getUsageTimestampUpdatePeriod() {
+ return getGarbageCleanupTimeout() / 4;
+ }
+
+ public void rescan(IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+
+ synchronized (this.automaticIndexingMutex) {
+ this.indexerDirtiedWhileDisabled = false;
+ }
+
+ long startTimeNs = System.nanoTime();
+ long currentTimeMs = System.currentTimeMillis();
+ if (DEBUG) {
+ Package.logInfo("Indexer running rescan"); //$NON-NLS-1$
+ }
+
+ // Gather all the IPackageFragmentRoots in the workspace
+ List<IJavaElement> unfilteredIndexables = getAllIndexableObjectsInWorkspace(subMonitor.split(3));
+
+ int totalIndexables = unfilteredIndexables.size();
+ // Remove all duplicate indexables (jars which are referenced by more than one project)
+ Map<IPath, List<IJavaElement>> allIndexables = removeDuplicatePaths(unfilteredIndexables);
+
+ long startGarbageCollectionNs = System.nanoTime();
+
+ // Remove all files in the index which aren't referenced in the workspace
+ int gcFiles = cleanGarbage(currentTimeMs, allIndexables.keySet(), subMonitor.split(4));
+
+ long startFingerprintTestNs = System.nanoTime();
+
+ Map<IPath, FingerprintTestResult> fingerprints = testFingerprints(allIndexables.keySet(), subMonitor.split(7));
+ Set<IPath> indexablesWithChanges = new HashSet<>(getIndexablesThatHaveChanged(allIndexables.keySet(), fingerprints));
+
+ long startIndexingNs = System.nanoTime();
+
+ int classesIndexed = 0;
+ SubMonitor loopMonitor = subMonitor.split(80).setWorkRemaining(indexablesWithChanges.size());
+ for (IPath next : indexablesWithChanges) {
+ classesIndexed += rescanArchive(currentTimeMs, next, allIndexables.get(next), fingerprints.get(next).getNewFingerprint(),
+ loopMonitor.split(1));
+ }
+
+ long endIndexingNs = System.nanoTime();
+
+ Map<IPath, List<IJavaElement>> pathsToUpdate = new HashMap<>();
+
+ for (IPath next : allIndexables.keySet()) {
+ if (!indexablesWithChanges.contains(next)) {
+ pathsToUpdate.put(next, allIndexables.get(next));
+ continue;
+ }
+ }
+
+ updateResourceMappings(pathsToUpdate, subMonitor.split(5));
+
+ // Flush the database to disk
+ this.nd.acquireWriteLock(subMonitor.split(4));
+ try {
+ this.nd.getDB().flush();
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+
+ fireDelta(indexablesWithChanges, subMonitor.split(1));
+
+ if (DEBUG) {
+ Package.logInfo("Rescan finished"); //$NON-NLS-1$
+ }
+
+ long endResourceMappingNs = System.nanoTime();
+
+ long fingerprintTimeMs = (startIndexingNs - startFingerprintTestNs) / MS_TO_NS;
+ long locateIndexablesTimeMs = (startGarbageCollectionNs - startTimeNs) / MS_TO_NS;
+ long garbageCollectionMs = (startFingerprintTestNs - startGarbageCollectionNs) / MS_TO_NS;
+ long indexingTimeMs = (endIndexingNs - startIndexingNs) / MS_TO_NS;
+ long resourceMappingTimeMs = (endResourceMappingNs - endIndexingNs) / MS_TO_NS;
+
+ double averageGcTimeMs = gcFiles == 0 ? 0 : (double)garbageCollectionMs / (double)gcFiles;
+ double averageIndexTimeMs = classesIndexed == 0 ? 0 : (double)indexingTimeMs / (double)classesIndexed;
+ double averageFingerprintTimeMs = allIndexables.size() == 0 ? 0 : (double)fingerprintTimeMs / (double)allIndexables.size();
+ double averageResourceMappingMs = pathsToUpdate.size() == 0 ? 0 : (double)resourceMappingTimeMs / (double)pathsToUpdate.size();
+
+ if (DEBUG_TIMING) {
+ Package.logInfo(
+ "Indexing done.\n" //$NON-NLS-1$
+ + " Located " + totalIndexables + " indexables in " + locateIndexablesTimeMs + "ms\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ + " Collected garbage from " + gcFiles + " files in " + garbageCollectionMs + "ms, average time = " + averageGcTimeMs + "ms\n" //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+ + " Tested " + allIndexables.size() + " fingerprints in " + fingerprintTimeMs + "ms, average time = " + averageFingerprintTimeMs + "ms\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ + " Indexed " + classesIndexed + " classes in " + indexingTimeMs + "ms, average time = " + averageIndexTimeMs + "ms\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ + " Updated " + pathsToUpdate.size() + " paths in " + resourceMappingTimeMs + "ms, average time = " + averageResourceMappingMs + "ms\n"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+ }
+
+ if (DEBUG_ALLOCATIONS) {
+ try (IReader readLock = this.nd.acquireReadLock()) {
+ this.nd.getDB().reportFreeBlocks();
+ this.nd.getDB().getMemoryStats().printMemoryStats(this.nd.getTypeRegistry());
+ }
+ }
+ }
+
+ private void fireDelta(Set<IPath> indexablesWithChanges, IProgressMonitor monitor) {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 1);
+ IProject[] projects = this.root.getProjects();
+
+ List<IProject> projectsToScan = new ArrayList<>();
+
+ for (IProject next : projects) {
+ if (next.isOpen()) {
+ projectsToScan.add(next);
+ }
+ }
+ JavaModel model = JavaModelManager.getJavaModelManager().getJavaModel();
+ boolean hasChanges = false;
+ JavaElementDelta delta = new JavaElementDelta(model);
+ SubMonitor projectLoopMonitor = subMonitor.split(1).setWorkRemaining(projectsToScan.size());
+ for (IProject project : projectsToScan) {
+ projectLoopMonitor.split(1);
+ try {
+ if (project.isOpen() && project.isNatureEnabled(JavaCore.NATURE_ID)) {
+ IJavaProject javaProject = JavaCore.create(project);
+
+ IPackageFragmentRoot[] roots = javaProject.getAllPackageFragmentRoots();
+
+ for (IPackageFragmentRoot next : roots) {
+ if (next.isArchive()) {
+ IPath location = JavaIndex.getLocationForElement(next);
+
+ if (indexablesWithChanges.contains(location)) {
+ hasChanges = true;
+ delta.changed(next,
+ IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED);
+ }
+ }
+ }
+ }
+ } catch (CoreException e) {
+ Package.log(e);
+ }
+ }
+
+ if (hasChanges) {
+ fireChange(IndexerEvent.createChange(delta));
+ }
+ }
+
+ private void updateResourceMappings(Map<IPath, List<IJavaElement>> pathsToUpdate, IProgressMonitor monitor) {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, pathsToUpdate.keySet().size());
+
+ JavaIndex index = JavaIndex.getIndex(this.nd);
+
+ for (Entry<IPath, List<IJavaElement>> entry : pathsToUpdate.entrySet()) {
+ SubMonitor iterationMonitor = subMonitor.split(1).setWorkRemaining(10);
+
+ this.nd.acquireWriteLock(iterationMonitor.split(1));
+ try {
+ NdResourceFile resourceFile = index.getResourceFile(entry.getKey().toString().toCharArray());
+ if (resourceFile == null) {
+ continue;
+ }
+
+ attachWorkspaceFilesToResource(entry.getValue(), resourceFile);
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+
+ }
+ }
+
+ /**
+ * Clean up unneeded files here, but only do so if it's been a long time since the file was last referenced. Being
+ * too eager about removing old files means that operations which temporarily cause a file to become unreferenced
+ * will run really slowly. also eagerly clean up any partially-indexed files we discover during the scan. That is,
+ * if we discover a file with a timestamp of 0, it indicates that the indexer or all of Eclipse crashed midway
+ * through indexing the file. Such garbage should be cleaned up as soon as possible, since it will never be useful.
+ *
+ * @param currentTimeMillis timestamp of the time at which the indexing operation started
+ * @param allIndexables list of all referenced java roots
+ * @param monitor progress monitor
+ * @return the number of indexables in the index, prior to garbage collection
+ */
+ private int cleanGarbage(long currentTimeMillis, Collection<IPath> allIndexables, IProgressMonitor monitor) {
+ JavaIndex index = JavaIndex.getIndex(this.nd);
+
+ int result = 0;
+ HashSet<IPath> paths = new HashSet<>();
+ paths.addAll(allIndexables);
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 3);
+
+ List<NdResourceFile> garbage = new ArrayList<>();
+ List<NdResourceFile> needsUpdate = new ArrayList<>();
+
+ long usageTimestampUpdatePeriod = getUsageTimestampUpdatePeriod();
+ long garbageCleanupTimeout = getGarbageCleanupTimeout();
+ // Build up the list of NdResourceFiles that either need to be garbage collected or
+ // have their read timestamps updated.
+ try (IReader reader = this.nd.acquireReadLock()) {
+ List<NdResourceFile> resourceFiles = index.getAllResourceFiles();
+
+ result = resourceFiles.size();
+ SubMonitor testMonitor = subMonitor.split(1).setWorkRemaining(resourceFiles.size());
+ for (NdResourceFile next : resourceFiles) {
+ testMonitor.split(1);
+ if (!next.isDoneIndexing()) {
+ garbage.add(next);
+ } else {
+ IPath nextPath = new Path(next.getLocation().toString());
+ long timeLastUsed = next.getTimeLastUsed();
+ long timeSinceLastUsed = currentTimeMillis - timeLastUsed;
+
+ if (paths.contains(nextPath)) {
+ if (timeSinceLastUsed > usageTimestampUpdatePeriod) {
+ needsUpdate.add(next);
+ }
+ } else {
+ if (timeSinceLastUsed > garbageCleanupTimeout) {
+ garbage.add(next);
+ }
+ }
+ }
+ }
+ }
+
+ SubMonitor deleteMonitor = subMonitor.split(1).setWorkRemaining(garbage.size());
+ for (NdResourceFile next : garbage) {
+ deleteResource(next, deleteMonitor.split(1));
+ }
+
+ SubMonitor updateMonitor = subMonitor.split(1).setWorkRemaining(needsUpdate.size());
+ for (NdResourceFile next : needsUpdate) {
+ this.nd.acquireWriteLock(updateMonitor.split(1));
+ try {
+ if (next.isInIndex()) {
+ next.setTimeLastUsed(currentTimeMillis);
+ }
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Performs a non-atomic delete of the given resource file. First, it marks the file as being invalid
+ * (by clearing out its timestamp). Then it deletes the children of the resource file, one child at a time.
+ * Once all the children are deleted, the resource itself is deleted. The result on the database is exactly
+ * the same as if the caller had called toDelete.delete(), but doing it this way ensures that a write lock
+ * will never be held for a nontrivial amount of time.
+ */
+ protected void deleteResource(NdResourceFile toDelete, IProgressMonitor monitor) {
+ SubMonitor deletionMonitor = SubMonitor.convert(monitor, 10);
+
+ this.nd.acquireWriteLock(deletionMonitor.split(1));
+ try {
+ if (toDelete.isInIndex()) {
+ toDelete.markAsInvalid();
+ }
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+
+ for (;;) {
+ this.nd.acquireWriteLock(deletionMonitor.split(1));
+ try {
+ if (!toDelete.isInIndex()) {
+ break;
+ }
+
+ int numChildren = toDelete.getBindingCount();
+ deletionMonitor.setWorkRemaining(numChildren + 1);
+ if (numChildren == 0) {
+ break;
+ }
+
+ NdBinding nextDeletion = toDelete.getBinding(numChildren - 1);
+ if (DEBUG_INSERTIONS) {
+ if (nextDeletion instanceof NdType) {
+ NdType type = (NdType)nextDeletion;
+ Package.logInfo("Deleting " + type.getTypeId().getFieldDescriptor().getString() + " from " //$NON-NLS-1$//$NON-NLS-2$
+ + new String(toDelete.getLocation().getString()) + " " + toDelete.address); //$NON-NLS-1$
+ }
+ }
+ nextDeletion.delete();
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+ }
+
+ this.nd.acquireWriteLock(deletionMonitor.split(1));
+ try {
+ if (toDelete.isInIndex()) {
+ toDelete.delete();
+ }
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+ }
+
+ private Map<IPath, List<IJavaElement>> removeDuplicatePaths(List<IJavaElement> allIndexables) {
+ Map<IPath, List<IJavaElement>> paths = new HashMap<>();
+
+ HashSet<IPath> workspacePaths = new HashSet<IPath>();
+ for (IJavaElement next : allIndexables) {
+ IPath nextPath = JavaIndex.getLocationForElement(next);
+ IPath workspacePath = getWorkspacePathForRoot(next);
+
+ List<IJavaElement> value = paths.get(nextPath);
+
+ if (value == null) {
+ value = new ArrayList<IJavaElement>();
+ paths.put(nextPath, value);
+ } else {
+ if (workspacePath != null) {
+ if (workspacePaths.contains(workspacePath)) {
+ continue;
+ }
+ if (!workspacePath.isEmpty()) {
+ Package.logInfo("Found duplicate workspace path for " + workspacePath.toString()); //$NON-NLS-1$
+ }
+ workspacePaths.add(workspacePath);
+ }
+ }
+
+ value.add(next);
+ }
+
+ return paths;
+ }
+
+ private IPath getWorkspacePathForRoot(IJavaElement next) {
+ IResource resource = next.getResource();
+ if (resource != null) {
+ return resource.getFullPath();
+ }
+ return Path.EMPTY;
+ }
+
+ private Map<IPath, FingerprintTestResult> testFingerprints(Collection<IPath> allIndexables,
+ IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, allIndexables.size());
+ Map<IPath, FingerprintTestResult> result = new HashMap<>();
+
+ for (IPath next : allIndexables) {
+ result.put(next, testForChanges(next, subMonitor.split(1)));
+ }
+
+ return result;
+ }
+
+ /**
+ * Rescans an archive (a jar, zip, or class file on the filesystem). Returns the number of classes indexed.
+ * @throws JavaModelException
+ */
+ private int rescanArchive(long currentTimeMillis, IPath thePath, List<IJavaElement> elementsMappingOntoLocation,
+ FileFingerprint fingerprint, IProgressMonitor monitor) throws JavaModelException {
+ if (elementsMappingOntoLocation.isEmpty()) {
+ return 0;
+ }
+
+ IJavaElement element = elementsMappingOntoLocation.get(0);
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+
+ String pathString = thePath.toString();
+ JavaIndex javaIndex = JavaIndex.getIndex(this.nd);
+
+ File theFile = thePath.toFile();
+ if (!(theFile.exists() && theFile.isFile())) {
+ if (DEBUG) {
+ Package.log("the file " + pathString + " does not exist", null); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return 0;
+ }
+
+ NdResourceFile resourceFile;
+
+ this.nd.acquireWriteLock(subMonitor.split(5));
+ try {
+ resourceFile = new NdResourceFile(this.nd);
+ resourceFile.setTimeLastUsed(currentTimeMillis);
+ resourceFile.setLocation(pathString);
+ IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) element
+ .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
+ IPath rootPathString = JavaIndex.getLocationForElement(packageFragmentRoot);
+ if (!rootPathString.equals(thePath)) {
+ resourceFile.setPackageFragmentRoot(rootPathString.toString().toCharArray());
+ }
+ attachWorkspaceFilesToResource(elementsMappingOntoLocation, resourceFile);
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+
+ if (DEBUG) {
+ Package.logInfo("rescanning " + thePath.toString() + ", " + fingerprint); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ int result;
+ try {
+ result = addElement(resourceFile, element, subMonitor.split(50));
+ } catch (JavaModelException e) {
+ if (DEBUG) {
+ Package.log("the file " + pathString + " cannot be indexed due to a recoverable error", null); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ // If this file can't be indexed due to a recoverable error, delete the NdResourceFile entry for it.
+ this.nd.acquireWriteLock(subMonitor.split(5));
+ try {
+ if (resourceFile.isInIndex()) {
+ resourceFile.delete();
+ }
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+ return 0;
+ } catch (RuntimeException e) {
+ if (DEBUG) {
+ Package.log("A RuntimeException occurred while indexing " + pathString, e); //$NON-NLS-1$
+ }
+ throw e;
+ }
+
+ List<NdResourceFile> allResourcesWithThisPath = Collections.emptyList();
+ // Now update the timestamp and delete all older versions of this resource that exist in the index
+ this.nd.acquireWriteLock(subMonitor.split(1));
+ try {
+ if (resourceFile.isInIndex()) {
+ resourceFile.setFingerprint(fingerprint);
+ allResourcesWithThisPath = javaIndex.findResourcesWithPath(pathString);
+ }
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+
+ SubMonitor deletionMonitor = subMonitor.split(40).setWorkRemaining(allResourcesWithThisPath.size() - 1);
+ for (NdResourceFile next : allResourcesWithThisPath) {
+ if (!next.equals(resourceFile)) {
+ deleteResource(next, deletionMonitor.split(1));
+ }
+ }
+
+ return result;
+ }
+
+ private void attachWorkspaceFilesToResource(List<IJavaElement> elementsMappingOntoLocation,
+ NdResourceFile resourceFile) {
+ for (IJavaElement next : elementsMappingOntoLocation) {
+ IResource nextResource = next.getResource();
+ if (nextResource != null) {
+ new NdWorkspaceLocation(this.nd, resourceFile,
+ nextResource.getFullPath().toString().toCharArray());
+ }
+ }
+ }
+
+ /**
+ * Adds an archive to the index, under the given NdResourceFile.
+ */
+ private int addElement(NdResourceFile resourceFile, IJavaElement element, IProgressMonitor monitor)
+ throws JavaModelException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor);
+
+ if (element instanceof JarPackageFragmentRoot) {
+ JarPackageFragmentRoot jarRoot = (JarPackageFragmentRoot) element;
+
+ IPath workspacePath = jarRoot.getPath();
+ IPath location = JavaIndex.getLocationForElement(jarRoot);
+
+ int classesIndexed = 0;
+ try (ZipFile zipFile = new ZipFile(JavaModelManager.getLocalFile(jarRoot.getPath()))) {
+ // Used for the error-handling unit tests
+ if (JavaModelManager.throwIoExceptionsInGetZipFile) {
+ if (DEBUG) {
+ Package.logInfo("Throwing simulated IOException for error handling test case"); //$NON-NLS-1$
+ }
+ throw new IOException();
+ }
+ subMonitor.setWorkRemaining(zipFile.size());
+
+ for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
+ SubMonitor nextEntry = subMonitor.split(1).setWorkRemaining(2);
+ ZipEntry member = e.nextElement();
+ if (member.isDirectory()) {
+ continue;
+ }
+ nextEntry.split(1);
+ String fileName = member.getName();
+
+ boolean classFileName = org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(fileName);
+ if (classFileName) {
+ String binaryName = fileName.substring(0, fileName.length() - SuffixConstants.SUFFIX_STRING_class.length());
+ char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(binaryName.toCharArray());
+ String indexPath = jarRoot.getHandleIdentifier() + IDependent.JAR_FILE_ENTRY_SEPARATOR + binaryName;
+ BinaryTypeDescriptor descriptor = new BinaryTypeDescriptor(location.toString().toCharArray(), fieldDescriptor,
+ workspacePath.toString().toCharArray(), indexPath.toCharArray());
+ try {
+ byte[] contents = org.eclipse.jdt.internal.compiler.util.Util.getZipEntryByteContent(member, zipFile);
+ ClassFileReader classFileReader = new ClassFileReader(contents, descriptor.indexPath, true);
+ if (addClassToIndex(resourceFile, descriptor.fieldDescriptor, descriptor.indexPath,
+ classFileReader, nextEntry.split(1))) {
+ classesIndexed++;
+ }
+ } catch (CoreException | ClassFormatException exception) {
+ Package.log("Unable to index " + descriptor.toString(), exception); //$NON-NLS-1$
+ }
+ }
+ }
+ } catch (ZipException e) {
+ Package.log("The zip file " + jarRoot.getPath() + " was corrupt", e); //$NON-NLS-1$//$NON-NLS-2$
+ // Indicates a corrupt zip file. Treat this like an empty zip file.
+ } catch (IOException ioException) {
+ throw new JavaModelException(ioException, IJavaModelStatusConstants.IO_EXCEPTION);
+ } catch (CoreException coreException) {
+ throw new JavaModelException(coreException);
+ }
+
+ if (DEBUG && classesIndexed == 0) {
+ Package.logInfo("The path " + element.getPath() + " contained no class files"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return classesIndexed;
+ } else if (element instanceof IClassFile) {
+ IClassFile classFile = (IClassFile)element;
+
+ SubMonitor iterationMonitor = subMonitor.split(1);
+ BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(classFile);
+
+ boolean indexed = false;
+ try {
+ ClassFileReader classFileReader = BinaryTypeFactory.rawReadType(descriptor, true);
+ if (classFileReader != null) {
+ indexed = addClassToIndex(resourceFile, descriptor.fieldDescriptor, descriptor.indexPath,
+ classFileReader, iterationMonitor);
+ }
+ } catch (CoreException | ClassFormatException e) {
+ Package.log("Unable to index " + classFile.toString(), e); //$NON-NLS-1$
+ }
+
+ return indexed ? 1 : 0;
+ } else {
+ Package.logInfo("Unable to index elements of type " + element); //$NON-NLS-1$
+ return 0;
+ }
+ }
+
+ private boolean addClassToIndex(NdResourceFile resourceFile, char[] fieldDescriptor, char[] indexPath,
+ ClassFileReader binaryType, IProgressMonitor monitor) throws ClassFormatException, CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+ ClassFileToIndexConverter converter = new ClassFileToIndexConverter(resourceFile);
+
+ boolean indexed = false;
+ this.nd.acquireWriteLock(subMonitor.split(5));
+ try {
+ if (resourceFile.isInIndex()) {
+ if (DEBUG_INSERTIONS) {
+ Package.logInfo("Inserting " + new String(fieldDescriptor) + " into " + resourceFile.getLocation().getString() + " " + resourceFile.address); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+ }
+ converter.addType(binaryType, fieldDescriptor, subMonitor.split(45));
+ indexed = true;
+ }
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+
+ if (DEBUG_SELFTEST && indexed) {
+ // When this debug flag is on, we test everything written to the index by reading it back immediately after indexing
+ // and comparing it with the original class file.
+ JavaIndex index = JavaIndex.getIndex(this.nd);
+ try (IReader readLock = this.nd.acquireReadLock()) {
+ NdTypeId typeId = index.findType(fieldDescriptor);
+ NdType targetType = null;
+ if (typeId != null) {
+ List<NdType> implementations = typeId.getTypes();
+ for (NdType nextType : implementations) {
+ NdResourceFile nextResourceFile = nextType.getResourceFile();
+ if (nextResourceFile.equals(resourceFile)) {
+ targetType = nextType;
+ break;
+ }
+ }
+ }
+
+ if (targetType != null) {
+ IndexBinaryType actualType = new IndexBinaryType(TypeRef.create(targetType), indexPath);
+ IndexTester.testType(binaryType, actualType);
+ } else {
+ Package.logInfo("Could not find class in index immediately after indexing it: " + new String(indexPath)); //$NON-NLS-1$
+ }
+ } catch (RuntimeException e) {
+ Package.log("Error during indexing: " + new String(indexPath), e); //$NON-NLS-1$
+ }
+ }
+ return indexed;
+ }
+
+ private List<IJavaElement> getAllIndexableObjectsInWorkspace(IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 2);
+ List<IJavaElement> allIndexables = new ArrayList<>();
+ IProject[] projects = this.root.getProjects();
+
+ List<IProject> projectsToScan = new ArrayList<>();
+
+ for (IProject next : projects) {
+ if (next.isOpen()) {
+ projectsToScan.add(next);
+ }
+ }
+
+ Set<IPath> scannedPaths = new HashSet<>();
+ Set<IResource> resourcesToScan = new HashSet<>();
+ SubMonitor projectLoopMonitor = subMonitor.split(1).setWorkRemaining(projectsToScan.size());
+ for (IProject project : projectsToScan) {
+ SubMonitor iterationMonitor = projectLoopMonitor.split(1);
+ try {
+ if (project.isOpen() && project.isNatureEnabled(JavaCore.NATURE_ID)) {
+ IJavaProject javaProject = JavaCore.create(project);
+
+ IClasspathEntry[] entries = javaProject.getRawClasspath();
+
+ if (EXPERIMENTAL_INDEX_OUTPUT_FOLDERS) {
+ IPath defaultOutputLocation = javaProject.getOutputLocation();
+ for (IClasspathEntry next : entries) {
+ IPath nextOutputLocation = next.getOutputLocation();
+
+ if (nextOutputLocation == null) {
+ nextOutputLocation = defaultOutputLocation;
+ }
+
+ IResource resource = this.root.findMember(nextOutputLocation);
+ if (resource != null) {
+ resourcesToScan.add(resource);
+ }
+ }
+ }
+
+ IPackageFragmentRoot[] projectRoots = javaProject.getAllPackageFragmentRoots();
+ SubMonitor rootLoopMonitor = iterationMonitor.setWorkRemaining(projectRoots.length);
+ for (IPackageFragmentRoot nextRoot : projectRoots) {
+ rootLoopMonitor.split(1);
+ if (!nextRoot.exists()) {
+ continue;
+ }
+ IPath filesystemPath = JavaIndex.getLocationForElement(nextRoot);
+ if (scannedPaths.contains(filesystemPath)) {
+ continue;
+ }
+ scannedPaths.add(filesystemPath);
+ if (nextRoot.getKind() == IPackageFragmentRoot.K_BINARY) {
+ if (nextRoot.isArchive()) {
+ allIndexables.add(nextRoot);
+ } else {
+ collectAllClassFiles(allIndexables, nextRoot);
+ }
+ } else {
+ collectAllClassFiles(allIndexables, nextRoot);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ Package.log(e);
+ }
+ }
+
+ collectAllClassFiles(allIndexables, resourcesToScan, subMonitor.split(1));
+ return allIndexables;
+ }
+
+ private void collectAllClassFiles(List<? super IClassFile> result, Collection<? extends IResource> toScan,
+ IProgressMonitor monitor) {
+ SubMonitor subMonitor = SubMonitor.convert(monitor);
+
+ ArrayDeque<IResource> resources = new ArrayDeque<>();
+ resources.addAll(toScan);
+
+ while (!resources.isEmpty()) {
+ subMonitor.setWorkRemaining(Math.max(resources.size(), 3000)).split(1);
+ IResource next = resources.removeFirst();
+
+ if (next instanceof IContainer) {
+ IContainer container = (IContainer)next;
+
+ try {
+ for (IResource nextChild : container.members()) {
+ resources.addLast(nextChild);
+ }
+ } catch (CoreException e) {
+ // If an error occurs in one resource, skip it and move on to the next
+ Package.log(e);
+ }
+ } else if (next instanceof IFile) {
+ IFile file = (IFile) next;
+
+ String extension = file.getFileExtension();
+ if (Objects.equals(extension, "class")) { //$NON-NLS-1$
+ IJavaElement element = JavaCore.create(file);
+
+ if (element instanceof IClassFile) {
+ result.add((IClassFile)element);
+ }
+ }
+ }
+ }
+ }
+
+ private void collectAllClassFiles(List<? super IClassFile> result, IParent nextRoot) throws CoreException {
+ for (IJavaElement child : nextRoot.getChildren()) {
+ try {
+ int type = child.getElementType();
+ if (!child.exists()) {
+ continue;
+ }
+ if (type == IJavaElement.COMPILATION_UNIT) {
+ continue;
+ }
+
+ if (type == IJavaElement.CLASS_FILE) {
+ result.add((IClassFile)child);
+ } else if (child instanceof IParent) {
+ IParent parent = (IParent) child;
+
+ collectAllClassFiles(result, parent);
+ }
+ } catch (CoreException e) {
+ // Log exceptions, then continue with the next child
+ Package.log(e);
+ }
+ }
+ }
+
+ /**
+ * Given a list of fragment roots, returns the subset of roots that have changed since the last time they were
+ * indexed.
+ */
+ private List<IPath> getIndexablesThatHaveChanged(Collection<IPath> indexables,
+ Map<IPath, FingerprintTestResult> fingerprints) {
+ List<IPath> indexablesWithChanges = new ArrayList<>();
+ for (IPath next : indexables) {
+ FingerprintTestResult testResult = fingerprints.get(next);
+
+ if (!testResult.matches()) {
+ indexablesWithChanges.add(next);
+ }
+ }
+ return indexablesWithChanges;
+ }
+
+ private FingerprintTestResult testForChanges(IPath thePath, IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+ JavaIndex javaIndex = JavaIndex.getIndex(this.nd);
+ String pathString = thePath.toString();
+
+ subMonitor.split(50);
+ NdResourceFile resourceFile = null;
+ FileFingerprint fingerprint = FileFingerprint.getEmpty();
+ this.nd.acquireReadLock();
+ try {
+ resourceFile = javaIndex.getResourceFile(pathString.toCharArray());
+
+ if (resourceFile != null) {
+ fingerprint = resourceFile.getFingerprint();
+ }
+ } finally {
+ this.nd.releaseReadLock();
+ }
+
+ FingerprintTestResult result = fingerprint.test(thePath, subMonitor.split(40));
+
+ // If this file hasn't changed but its timestamp has, write an updated fingerprint to the database
+ if (resourceFile != null && result.matches() && result.needsNewFingerprint()) {
+ this.nd.acquireWriteLock(subMonitor.split(10));
+ try {
+ if (resourceFile.isInIndex()) {
+ if (DEBUG) {
+ Package.logInfo(
+ "Writing updated fingerprint for " + thePath + ": " + result.getNewFingerprint()); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ resourceFile.setFingerprint(result.getNewFingerprint());
+ }
+ } finally {
+ this.nd.releaseWriteLock();
+ }
+ }
+ return result;
+ }
+
+ public Indexer(Nd toPopulate, IWorkspaceRoot workspaceRoot) {
+ this.nd = toPopulate;
+ this.root = workspaceRoot;
+ }
+
+ public void rescanAll() {
+ if (DEBUG) {
+ Package.logInfo("Scheduling rescanAll now"); //$NON-NLS-1$
+ }
+ synchronized (this.automaticIndexingMutex) {
+ if (!this.enableAutomaticIndexing) {
+ if (!this.indexerDirtiedWhileDisabled) {
+ this.indexerDirtiedWhileDisabled = true;
+ }
+ return;
+ }
+ }
+ this.rescanJob.schedule();
+ }
+
+ /**
+ * Adds the given listener. It will be notified when Nd changes. No strong references
+ * will be retained to the listener.
+ */
+ public void addListener(Listener newListener) {
+ synchronized (this.listenersMutex) {
+ Set<Listener> oldListeners = this.listeners;
+ this.listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>());
+ this.listeners.addAll(oldListeners);
+ this.listeners.add(newListener);
+ }
+ }
+
+ public void removeListener(Listener oldListener) {
+ synchronized (this.listenersMutex) {
+ if (!this.listeners.contains(oldListener)) {
+ return;
+ }
+ Set<Listener> oldListeners = this.listeners;
+ this.listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>());
+ this.listeners.addAll(oldListeners);
+ this.listeners.remove(oldListener);
+ }
+ }
+
+ private void fireChange(IndexerEvent event) {
+ Set<Listener> localListeners;
+ synchronized (this.listenersMutex) {
+ localListeners = this.listeners;
+ }
+
+ for (Listener next : localListeners) {
+ next.consume(event);
+ }
+ }
+
+ public void waitForIndex(IProgressMonitor monitor) {
+ try {
+ boolean shouldRescan = false;
+ synchronized (this.automaticIndexingMutex) {
+ if (!this.enableAutomaticIndexing && this.indexerDirtiedWhileDisabled) {
+ shouldRescan = true;
+ }
+ }
+ if (shouldRescan) {
+ this.rescanJob.schedule();
+ }
+ this.rescanJob.join(0, monitor);
+ } catch (InterruptedException e) {
+ throw new OperationCanceledException();
+ }
+ }
+
+ public void waitForIndex(int waitingPolicy, IProgressMonitor monitor) {
+ switch (waitingPolicy) {
+ case IJob.ForceImmediate: {
+ break;
+ }
+ case IJob.CancelIfNotReady: {
+ if (this.rescanJob.getState() != Job.NONE) {
+ throw new OperationCanceledException();
+ }
+ break;
+ }
+ case IJob.WaitUntilReady: {
+ waitForIndex(monitor);
+ break;
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexerEvent.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexerEvent.java
new file mode 100644
index 000000000..1a29d5d2f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/IndexerEvent.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import org.eclipse.jdt.core.IJavaElementDelta;
+
+public class IndexerEvent {
+ final IJavaElementDelta delta;
+
+ private IndexerEvent(IJavaElementDelta delta) {
+ this.delta = delta;
+ }
+
+ public static IndexerEvent createChange(IJavaElementDelta delta) {
+ return new IndexerEvent(delta);
+ }
+
+ public IJavaElementDelta getDelta() {
+ return this.delta;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Messages.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Messages.java
new file mode 100644
index 000000000..78b4f9701
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Messages.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.jdt.internal.core.nd.indexer.messages"; //$NON-NLS-1$
+ public static String Indexer_updating_index_job_name;
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Package.java
new file mode 100644
index 000000000..df19a3c97
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/Package.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.indexer;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/* package */ class Package {
+ public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+ public static void log(Throwable e) {
+ String msg = e.getMessage();
+ if (msg == null) {
+ log("Error", e); //$NON-NLS-1$
+ } else {
+ log("Error: " + msg, e); //$NON-NLS-1$
+ }
+ }
+
+ public static void log(String message, Throwable e) {
+ log(createStatus(message, e));
+ }
+
+ public static IStatus createStatus(String msg, Throwable e) {
+ return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+ }
+
+ public static IStatus createStatus(String msg) {
+ return new Status(IStatus.ERROR, PLUGIN_ID, msg);
+ }
+
+ public static void logInfo(String message) {
+ log(new Status(IStatus.INFO, PLUGIN_ID, message));
+ }
+
+ public static void log(IStatus status) {
+ JavaCore.getPlugin().getLog().log(status);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/messages.properties b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/messages.properties
new file mode 100644
index 000000000..f835143ab
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/indexer/messages.properties
@@ -0,0 +1 @@
+Indexer_updating_index_job_name=Updating index
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/ClasspathResolver.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/ClasspathResolver.java
new file mode 100644
index 000000000..5e5f638b9
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/ClasspathResolver.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+/**
+ * Used for filtering and disambiguating bindings in the index to match the classpath.
+ */
+public interface ClasspathResolver {
+ public static final int NOT_ON_CLASSPATH = -1;
+
+ /**
+ * Returns the priority of the given resource file on the classpath or {@link #NOT_ON_CLASSPATH} if the given file
+ * is not onthe classpath. In the event that the same fully-qualified class name is found in multiple resource
+ * files, the one with the higher priority number is preferred.
+ */
+ int resolve(NdResourceFile sourceOfReference, NdResourceFile toTest);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/FileFingerprint.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/FileFingerprint.java
new file mode 100644
index 000000000..f1b02622f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/FileFingerprint.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileInfo;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jdt.internal.core.nd.StreamHasher;
+
+public class FileFingerprint {
+ /**
+ * Sentinel value for {@link #time} indicating a nonexistent fingerprint. This is used for the timestamp of
+ * nonexistent files and for the {@link #getEmpty()} singleton.
+ */
+ public static final long NEVER_MODIFIED = 0;
+
+ /**
+ * Sentinel value for {@link #time} indicating that the timestamp was not recorded as part of the fingerprint.
+ * This is normally used to indicate that the file's timestamp was so close to the current system time at the time
+ * the fingerprint was computed that subsequent changes in the file might not be detected. In such cases, timestamps
+ * are an unreliable method for determining if the file has changed and so are not included as part of the fingerprint.
+ */
+ public static final long UNKNOWN = 1;
+
+ /**
+ * Worst-case accuracy of filesystem timestamps, among all supported platforms (this is currently 1s on linux, 2s on
+ * FAT systems).
+ */
+ private static final long WORST_FILESYSTEM_TIMESTAMP_ACCURACY_MS = 2000;
+
+ private long time;
+ private long hash;
+ private long size;
+
+ private static final FileFingerprint EMPTY = new FileFingerprint(NEVER_MODIFIED,0,0);
+
+ public static final FileFingerprint getEmpty() {
+ return EMPTY;
+ }
+
+ public static final FileFingerprint create(IPath path, IProgressMonitor monitor) throws CoreException {
+ return getEmpty().test(path, monitor).getNewFingerprint();
+ }
+
+ public FileFingerprint(long time, long size, long hash) {
+ super();
+ this.time = time;
+ this.size = size;
+ this.hash = hash;
+ }
+
+ public long getTime() {
+ return this.time;
+ }
+
+ public long getHash() {
+ return this.hash;
+ }
+
+ public long getSize() {
+ return this.size;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (this.hash ^ (this.hash >>> 32));
+ result = prime * result + (int) (this.size ^ (this.size >>> 32));
+ result = prime * result + (int) (this.time ^ (this.time >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ FileFingerprint other = (FileFingerprint) obj;
+ if (this.hash != other.hash)
+ return false;
+ if (this.size != other.size)
+ return false;
+ if (this.time != other.time)
+ return false;
+ return true;
+ }
+
+ public static class FingerprintTestResult {
+ private boolean matches;
+ private boolean needsNewFingerprint;
+ private FileFingerprint newFingerprint;
+
+ public FingerprintTestResult(boolean matches, boolean needsNewFingerprint, FileFingerprint newFingerprint) {
+ super();
+ this.matches = matches;
+ this.newFingerprint = newFingerprint;
+ this.needsNewFingerprint = needsNewFingerprint;
+ }
+
+ public boolean needsNewFingerprint() {
+ return this.needsNewFingerprint;
+ }
+
+ public boolean matches() {
+ return this.matches;
+ }
+
+ public FileFingerprint getNewFingerprint() {
+ return this.newFingerprint;
+ }
+
+ @Override
+ public String toString() {
+ return "FingerprintTestResult [matches=" + this.matches + ", needsNewFingerprint=" //$NON-NLS-1$//$NON-NLS-2$
+ + this.needsNewFingerprint + ", newFingerprint=" + this.newFingerprint + "]"; //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+
+ /**
+ * Compares the given File with the receiver. If the fingerprint matches (ie: the file
+ */
+ public FingerprintTestResult test(IPath path, IProgressMonitor monitor) throws CoreException {
+ SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
+ long currentTime = System.currentTimeMillis();
+ IFileStore store = EFS.getLocalFileSystem().getStore(path);
+ IFileInfo fileInfo = store.fetchInfo();
+
+ long lastModified = fileInfo.getLastModified();
+ if (Math.abs(currentTime - lastModified) < WORST_FILESYSTEM_TIMESTAMP_ACCURACY_MS) {
+ // If the file was modified so recently that it's within our ability to measure it, don't include
+ // the timestamp as part of the fingerprint. If another change were to happen to the file immediately
+ // afterward, we might not be able to detect it using the timestamp.
+ lastModified = UNKNOWN;
+ }
+ subMonitor.split(5);
+
+ long fileSize = fileInfo.getLength();
+ subMonitor.split(5);
+ if (lastModified != UNKNOWN && lastModified == this.time && fileSize == this.size) {
+ return new FingerprintTestResult(true, false, this);
+ }
+
+ long hashCode;
+ try {
+ hashCode = fileSize == 0 ? 0 : computeHashCode(path.toFile(), fileSize, subMonitor.split(90));
+ } catch (IOException e) {
+ throw new CoreException(Package.createStatus("An error occurred computing a hash code", e)); //$NON-NLS-1$
+ }
+ boolean matches = (hashCode == this.hash && fileSize == this.size);
+
+ FileFingerprint newFingerprint = new FileFingerprint(lastModified, fileSize, hashCode);
+ return new FingerprintTestResult(matches, !equals(newFingerprint), newFingerprint);
+ }
+
+ private long computeHashCode(File toTest, long fileSize, IProgressMonitor monitor) throws IOException {
+ final int BUFFER_SIZE = 2048;
+ char[] charBuffer = new char[BUFFER_SIZE];
+ byte[] byteBuffer = new byte[BUFFER_SIZE * 2];
+
+ SubMonitor subMonitor = SubMonitor.convert(monitor, (int) (fileSize / (BUFFER_SIZE * 2)));
+ StreamHasher hasher = new StreamHasher();
+ try {
+ InputStream inputStream = new FileInputStream(toTest);
+ try {
+ while (true) {
+ subMonitor.split(1);
+ int bytesRead = readUntilBufferFull(inputStream, byteBuffer);
+
+ if (bytesRead < byteBuffer.length) {
+ charBuffer = new char[(bytesRead + 1) / 2];
+ copyByteArrayToCharArray(charBuffer, byteBuffer, bytesRead);
+ hasher.addChunk(charBuffer);
+ break;
+ }
+
+ copyByteArrayToCharArray(charBuffer, byteBuffer, bytesRead);
+ hasher.addChunk(charBuffer);
+ }
+ } finally {
+ inputStream.close();
+ }
+
+ } catch (FileNotFoundException e) {
+ return 0;
+ }
+
+ return hasher.computeHash();
+ }
+
+ private void copyByteArrayToCharArray(char[] charBuffer, byte[] byteBuffer, int bytesToCopy) {
+ for (int ch = 0; ch < bytesToCopy / 2; ch++) {
+ char next = (char) (byteBuffer[ch * 2] + byteBuffer[ch * 2 + 1]);
+ charBuffer[ch] = next;
+ }
+
+ if (bytesToCopy % 2 != 0) {
+ charBuffer[bytesToCopy / 2] = (char) byteBuffer[bytesToCopy - 1];
+ }
+ }
+
+ int readUntilBufferFull(InputStream inputStream, byte[] buffer) throws IOException {
+ int bytesRead = 0;
+ while (bytesRead < buffer.length) {
+ int thisRead = inputStream.read(buffer, bytesRead, buffer.length - bytesRead);
+
+ if (thisRead == -1) {
+ return bytesRead;
+ }
+
+ bytesRead += thisRead;
+ }
+ return bytesRead;
+ }
+
+ private static String getTimeString(long timestamp) {
+ if (timestamp == UNKNOWN) {
+ return "UNKNOWN"; //$NON-NLS-1$
+ } else if (timestamp == NEVER_MODIFIED) {
+ return "NEVER_MODIFIED"; //$NON-NLS-1$
+ }
+ return Long.toString(timestamp);
+ }
+
+ @Override
+ public String toString() {
+ return "FileFingerprint [time=" + getTimeString(this.time) + ", size=" + this.size + ", hash=" + this.hash + "]"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/IndexFilter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/IndexFilter.java
new file mode 100644
index 000000000..cc6a90d85
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/IndexFilter.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2016 Wind River Systems, Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Markus Schorn - initial API and implementation
+ * Andrew Ferguson (Symbian)
+ * Bryan Wilkinson (QNX)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.dom.IBinding;
+
+/**
+ * Can be subclassed and used for queries in the index.
+ */
+public class IndexFilter {
+ public static final IndexFilter ALL = new IndexFilter();
+
+ /**
+ * Get an IndexFilter that accepts everything
+ *
+ * @return an IndexFilter instance
+ */
+ public static IndexFilter getFilter() {
+ return new IndexFilter();
+ }
+
+ /**
+ * Determines whether or not a binding is valid.
+ *
+ * @param binding the binding being checked for validity
+ * @return whether or not the binding is valid
+ * @throws CoreException
+ */
+ public boolean acceptBinding(IBinding binding) throws CoreException {
+ return true;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
new file mode 100644
index 000000000..006aeff10
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaIndex.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.io.File;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.preferences.IPreferencesService;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.NdNodeTypeRegistry;
+import org.eclipse.jdt.internal.core.nd.db.ChunkCache;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.IResultRank;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.SearchCriteria;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+public class JavaIndex {
+ // Version constants
+ static final int CURRENT_VERSION = Nd.version(1, 37);
+ static final int MAX_SUPPORTED_VERSION = Nd.version(1, 37);
+ static final int MIN_SUPPORTED_VERSION = Nd.version(1, 37);
+
+ // Fields for the search header
+ public static final FieldSearchIndex<NdResourceFile> FILES;
+ public static final FieldSearchIndex<NdTypeId> SIMPLE_INDEX;
+ public static final FieldSearchIndex<NdTypeId> TYPES;
+ public static final FieldSearchIndex<NdMethodId> METHODS;
+
+ public static final StructDef<JavaIndex> type;
+
+ static {
+ type = StructDef.create(JavaIndex.class);
+ FILES = FieldSearchIndex.create(type, NdResourceFile.FILENAME);
+ SIMPLE_INDEX = FieldSearchIndex.create(type, NdTypeId.SIMPLE_NAME);
+ TYPES = FieldSearchIndex.create(type, NdTypeId.FIELD_DESCRIPTOR);
+ METHODS = FieldSearchIndex.create(type, NdMethodId.METHOD_NAME);
+ type.done();
+
+ // This struct needs to fit within the first database chunk.
+ assert type.getFactory().getRecordSize() <= Database.CHUNK_SIZE;
+ }
+
+ private final static class BestResourceFile implements FieldSearchIndex.IResultRank {
+ public BestResourceFile() {
+ }
+
+ @Override
+ public long getRank(Nd resourceFileNd, long resourceFileAddress) {
+ return NdResourceFile.TIME_LAST_SCANNED.get(resourceFileNd, resourceFileAddress);
+ }
+ }
+
+ private static final BestResourceFile bestResourceFile = new BestResourceFile();
+ private final long address;
+ private Nd nd;
+ private IResultRank anyResult = new IResultRank() {
+ @Override
+ public long getRank(Nd dom, long address1) {
+ return 1;
+ }
+ };
+ private static Nd globalNd;
+ private static final String INDEX_FILENAME = "index.db"; //$NON-NLS-1$
+ private final static Object ndMutex = new Object();
+
+ public JavaIndex(Nd dom, long address) {
+ this.address = address;
+ this.nd = dom;
+ }
+
+ /**
+ * Returns the most-recently-scanned resource file with the given name or null if none
+ */
+ public NdResourceFile getResourceFile(char[] location) {
+ return FILES.findBest(this.nd, this.address, FieldSearchIndex.SearchCriteria.create(location),
+ bestResourceFile);
+ }
+
+ /**
+ * Returns true iff the given resource file is up-to-date with the filesystem. Returns false
+ * if the argument is out-of-date with the file system or null.
+ *
+ * @param file the index file to look up or null
+ * @throws CoreException
+ */
+ public boolean isUpToDate(NdResourceFile file) throws CoreException {
+ if (file != null && file.isDoneIndexing()) {
+ // TODO(sxenos): It would be much more efficient to mark files as being in one
+ // of three states: unknown, dirty, or clean. Files would start in the unknown
+ // state and move into the dirty state when we see them in a java model change
+ // event. They would move into the clean state after passing this sort of
+ // fingerprint test... but by caching the state of all tested files (in memory),
+ // it would eliminate the vast majority of these (slow) fingerprint tests.
+
+ Path locationPath = new Path(file.getLocation().getString());
+ if (file.getFingerprint().test(locationPath, null).matches()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List<NdResourceFile> findResourcesWithPath(String thePath) {
+ return FILES.findAll(this.nd, this.address, FieldSearchIndex.SearchCriteria.create(thePath.toCharArray()));
+ }
+
+ public List<NdResourceFile> getAllResourceFiles() {
+ return FILES.asList(this.nd, this.address);
+ }
+
+ public NdTypeId findType(char[] fieldDescriptor) {
+ SearchCriteria searchCriteria = SearchCriteria.create(fieldDescriptor);
+ return TYPES.findBest(this.nd, this.address, searchCriteria, this.anyResult);
+ }
+
+ public boolean visitFieldDescriptorsStartingWith(char[] fieldDescriptorPrefix, FieldSearchIndex.Visitor<NdTypeId> visitor) {
+ SearchCriteria searchCriteria = SearchCriteria.create(fieldDescriptorPrefix).prefix(true);
+ return TYPES.visitAll(this.nd, this.address, searchCriteria, visitor);
+ }
+
+ /**
+ * Returns a type ID or creates a new one if it does not exist. The caller must
+ * attach a reference to it after calling this method or it may leak.
+ */
+ public NdTypeId createTypeId(char[] fieldDescriptor) {
+ NdTypeId existingType = findType(fieldDescriptor);
+
+ if (existingType != null) {
+ return existingType;
+ }
+
+ if (fieldDescriptor.length > 1) {
+ if (fieldDescriptor[0] == 'L') {
+ if (fieldDescriptor[fieldDescriptor.length - 1] != ';') {
+ throw new IllegalStateException(new String(fieldDescriptor) + " is not a valid field descriptor"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ NdTypeId result = new NdTypeId(this.nd, fieldDescriptor);
+ if (!CharArrayUtils.equals(result.getFieldDescriptor().getChars(), fieldDescriptor)) {
+ throw new IllegalStateException("Field descriptor didn't match"); //$NON-NLS-1$
+ }
+ return result;
+ }
+
+ public Nd getNd() {
+ return this.nd;
+ }
+
+ public NdMethodId findMethodId(char[] methodId) {
+ SearchCriteria searchCriteria = SearchCriteria.create(methodId);
+
+ return METHODS.findBest(this.nd, this.address, searchCriteria, this.anyResult);
+ }
+
+ public NdMethodId createMethodId(char[] methodId) {
+ NdMethodId existingMethod = findMethodId(methodId);
+
+ if (existingMethod != null) {
+ return existingMethod;
+ }
+
+ return new NdMethodId(this.nd, methodId);
+ }
+
+ /**
+ * Returns the absolute filesystem location of the given element or null if none
+ */
+ public static IPath getLocationForElement(IJavaElement next) {
+ IResource resource = next.getResource();
+
+ if (resource != null) {
+ return resource.getLocation() == null ? new Path("") : resource.getLocation(); //$NON-NLS-1$
+ }
+
+ return next.getPath();
+ }
+
+ public static boolean isEnabled() {
+ IPreferencesService preferenceService = Platform.getPreferencesService();
+ if (preferenceService == null) {
+ return true;
+ }
+ return !preferenceService.getBoolean(JavaCore.PLUGIN_ID, "disableNewJavaIndex", false, //$NON-NLS-1$
+ null);
+ }
+
+ public static Nd createNd(File databaseFile, ChunkCache chunkCache) {
+ return new Nd(databaseFile, chunkCache, createTypeRegistry(),
+ MIN_SUPPORTED_VERSION, MAX_SUPPORTED_VERSION, CURRENT_VERSION);
+ }
+
+ public static Nd getGlobalNd() {
+ Nd localNd;
+ synchronized (ndMutex) {
+ localNd = globalNd;
+ }
+
+ if (localNd != null) {
+ return localNd;
+ }
+
+ localNd = createNd(getDBFile(), ChunkCache.getSharedInstance());
+
+ synchronized (ndMutex) {
+ if (globalNd == null) {
+ globalNd = localNd;
+ }
+ return globalNd;
+ }
+ }
+
+ public static JavaIndex getIndex(Nd nd) {
+ return new JavaIndex(nd, Database.DATA_AREA_OFFSET);
+ }
+
+ public static JavaIndex getIndex() {
+ return getIndex(getGlobalNd());
+ }
+
+ public static int getCurrentVersion() {
+ return CURRENT_VERSION;
+ }
+
+ static File getDBFile() {
+ IPath stateLocation = JavaCore.getPlugin().getStateLocation();
+ return stateLocation.append(INDEX_FILENAME).toFile();
+ }
+
+ static NdNodeTypeRegistry<NdNode> createTypeRegistry() {
+ NdNodeTypeRegistry<NdNode> registry = new NdNodeTypeRegistry<>();
+ registry.register(0x0001, NdAnnotation.type.getFactory());
+ registry.register(0x0004, NdAnnotationInConstant.type.getFactory());
+ registry.register(0x0008, NdAnnotationInMethod.type.getFactory());
+ registry.register(0x000c, NdAnnotationInMethodParameter.type.getFactory());
+ registry.register(0x0010, NdAnnotationInType.type.getFactory());
+ registry.register(0x0014, NdAnnotationInVariable.type.getFactory());
+ registry.register(0x0020, NdAnnotationValuePair.type.getFactory());
+ registry.register(0x0028, NdBinding.type.getFactory());
+ registry.register(0x0030, NdComplexTypeSignature.type.getFactory());
+ registry.register(0x0038, NdConstant.type.getFactory());
+ registry.register(0x0040, NdConstantAnnotation.type.getFactory());
+ registry.register(0x0050, NdConstantArray.type.getFactory());
+ registry.register(0x0060, NdConstantBoolean.type.getFactory());
+ registry.register(0x0070, NdConstantByte.type.getFactory());
+ registry.register(0x0080, NdConstantChar.type.getFactory());
+ registry.register(0x0090, NdConstantClass.type.getFactory());
+ registry.register(0x00A0, NdConstantDouble.type.getFactory());
+ registry.register(0x00B0, NdConstantEnum.type.getFactory());
+ registry.register(0x00C0, NdConstantFloat.type.getFactory());
+ registry.register(0x00D0, NdConstantInt.type.getFactory());
+ registry.register(0x00E0, NdConstantLong.type.getFactory());
+ registry.register(0x00F0, NdConstantShort.type.getFactory());
+ registry.register(0x0100, NdConstantString.type.getFactory());
+ registry.register(0x0110, NdMethod.type.getFactory());
+ registry.register(0x0120, NdMethodException.type.getFactory());
+ registry.register(0x0130, NdMethodId.type.getFactory());
+ registry.register(0x0140, NdMethodParameter.type.getFactory());
+ registry.register(0x0150, NdResourceFile.type.getFactory());
+ registry.register(0x0160, NdTreeNode.type.getFactory());
+ registry.register(0x0170, NdType.type.getFactory());
+ registry.register(0x0180, NdTypeAnnotation.type.getFactory());
+ registry.register(0x0184, NdTypeAnnotationInMethod.type.getFactory());
+ registry.register(0x0188, NdTypeAnnotationInType.type.getFactory());
+ registry.register(0x018c, NdTypeAnnotationInVariable.type.getFactory());
+ registry.register(0x0190, NdTypeArgument.type.getFactory());
+ registry.register(0x0194, NdTypeBound.type.getFactory());
+ registry.register(0x01A0, NdTypeInterface.type.getFactory());
+ registry.register(0x01B0, NdTypeParameter.type.getFactory());
+ registry.register(0x01C0, NdTypeSignature.type.getFactory());
+ registry.register(0x01D0, NdTypeId.type.getFactory());
+ registry.register(0x01E0, NdTypeInterface.type.getFactory());
+ registry.register(0x01F0, NdVariable.type.getFactory());
+ registry.register(0x0200, NdWorkspaceLocation.type.getFactory());
+ return registry;
+ }
+
+ public void rebuildIndex() {
+ // TODO: delete and recreate the index
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaNames.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaNames.java
new file mode 100644
index 000000000..0211cb89c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/JavaNames.java
@@ -0,0 +1,222 @@
+package org.eclipse.jdt.internal.core.nd.java;
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class JavaNames {
+ private static final char[] CLASS_FILE_SUFFIX = ".class".toCharArray(); //$NON-NLS-1$
+ public static final char[] FIELD_DESCRIPTOR_PREFIX = new char[] { 'L' };
+ private static final char[] FIELD_DESCRIPTOR_SUFFIX = new char[] { ';' };
+ private static final char[] METHOD_ID_SEPARATOR = new char[] { '#' };
+ private static final char[] JAR_FILE_ENTRY_SEPARATOR = IJavaSearchScope.JAR_FILE_ENTRY_SEPARATOR.toCharArray();
+ public static final char[] ARRAY_FIELD_DESCRIPTOR_PREFIX = new char[] { '[' };
+
+ /**
+ * Converts a java binary name to a simple name.
+ */
+ public static char[] binaryNameToSimpleName(char[] binaryName) {
+ int skipIndex = Math.max(
+ Math.max(CharOperation.lastIndexOf('$', binaryName), CharOperation.lastIndexOf('.', binaryName)),
+ CharOperation.lastIndexOf('/', binaryName)) + 1;
+
+ return CharArrayUtils.subarray(binaryName, skipIndex);
+ }
+
+ /**
+ * Given the binary name of a class, returns the jar-relative path of the class file within that
+ * jar, including the .class extension.
+ */
+ public static char[] binaryNameToResourceRelativePath(char[] binaryName) {
+ return CharOperation.concat(binaryName, CLASS_FILE_SUFFIX);
+ }
+
+ public static char[] fullyQualifiedNameToBinaryName(char[] fullyQualifiedName) {
+ return CharOperation.replaceOnCopy(fullyQualifiedName, '.', '/');
+ }
+
+ public static char[] fullyQualifiedNameToFieldDescriptor(char[] fullyQualifiedName) {
+ char[] result = CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, fullyQualifiedName, FIELD_DESCRIPTOR_SUFFIX);
+ CharOperation.replace(result, '.', '/');
+ return result;
+ }
+
+ /**
+ * Given a NdType, returns its identifier in the form accepted by {@link IJavaSearchScope#encloses(String)}
+ */
+ public static char[] getIndexPathFor(NdType type, IWorkspaceRoot root) {
+ NdResourceFile resourceFile = type.getResourceFile();
+
+ char[] binaryName = type.getTypeId().getBinaryName();
+
+ char[] workspaceLocation = null;
+ if (root != null) {
+ workspaceLocation = resourceFile.getAnyOpenWorkspaceLocation(root).toString().toCharArray();
+ }
+
+ if (workspaceLocation == null || workspaceLocation.length == 0) {
+ workspaceLocation = resourceFile.getLocation().getChars();
+ }
+
+ return CharArrayUtils.concat(workspaceLocation, JAR_FILE_ENTRY_SEPARATOR,
+ binaryNameToResourceRelativePath(binaryName));
+ }
+
+ /**
+ * Converts a binary name to a field descriptor (without the trailing ';')
+ */
+ public static char[] binaryNameToFieldDescriptor(char[] binaryName) {
+ return CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, binaryName, FIELD_DESCRIPTOR_SUFFIX);
+ }
+
+ /**
+ * Converts a field descriptor to a simple class name. Returns null if the given field descriptor
+ * doesn't refer to a class or is badly-formed.
+ */
+ public static char[] fieldDescriptorToSimpleName(char[] fieldDescriptor) {
+ if (!CharArrayUtils.startsWith(fieldDescriptor, 'L')) {
+ return null;
+ }
+
+ if (!CharArrayUtils.endsWith(fieldDescriptor, ';')) {
+ return null;
+ }
+
+ int separatorPosition = CharArrayUtils.lastIndexOf('/', fieldDescriptor);
+ if (separatorPosition == -1) {
+ separatorPosition = 0;
+ }
+
+ char[] className = CharArrayUtils.subarray(fieldDescriptor, separatorPosition + 1, fieldDescriptor.length - 1);
+ return className;
+ }
+
+ /**
+ * Converts a field descriptor to a java name. If fullyQualified is true, it returns a fully qualified class name.
+ * If it is false, it returns a source name.
+ */
+ public static char[] fieldDescriptorToJavaName(char[] fieldDescriptor, boolean fullyQualified) {
+ int arrayCount = 0;
+ CharArrayBuffer result = new CharArrayBuffer();
+ for(int scanPosition = 0; scanPosition < fieldDescriptor.length; scanPosition++) {
+ char nextChar = fieldDescriptor[scanPosition];
+
+ switch (nextChar) {
+ case 'B' : result.append("byte"); break; //$NON-NLS-1$
+ case 'C' : result.append("char"); break; //$NON-NLS-1$
+ case 'D' : result.append("double"); break; //$NON-NLS-1$
+ case 'F' : result.append("float"); break; //$NON-NLS-1$
+ case 'I' : result.append("int"); break; //$NON-NLS-1$
+ case 'J' : result.append("long"); break; //$NON-NLS-1$
+ case 'L' : {
+ int end = fieldDescriptor.length - 1;
+ char[] binaryName = CharArrayUtils.subarray(fieldDescriptor, scanPosition + 1, end);
+ if (fullyQualified) {
+ // Modify the binaryName string in-place to change it into a fully qualified name
+ CharOperation.replace(binaryName, '/', '.');
+ result.append(binaryName);
+ } else {
+ result.append(binaryNameToSimpleName(binaryName));
+ }
+ scanPosition += binaryName.length;
+ break;
+ }
+ case 'S' : result.append("short"); break; //$NON-NLS-1$
+ case 'Z' : result.append("boolean"); break; //$NON-NLS-1$
+ case '[' : arrayCount++; break;
+ }
+ }
+
+ while (--arrayCount >= 0) {
+ result.append("[]"); //$NON-NLS-1$
+ }
+
+ return CharArrayUtils.notNull(result.getContents());
+ }
+
+ public static char[] binaryNameToFullyQualifiedName(char[] binaryName) {
+ return CharOperation.replaceOnCopy(binaryName, '/', '.');
+ }
+
+ /**
+ * Returns a method id (suitable for constructing a {@link NdMethodId}) given a field descriptor for its parent type
+ * and a combined method selector and method descriptor for the method
+ *
+ * @param parentTypeBinaryName a field descriptor of the sort returned by the other *ToFieldDescriptor methods.
+ * @param methodSelectorAndDescriptor a method selector and descriptor of the form returned by {@link IBinaryType#getEnclosingMethod()}
+ * @return a method id suitable for looking up a {@link NdMethodId}
+ */
+ public static char[] getMethodId(char[] parentTypeBinaryName, char[] methodSelectorAndDescriptor) {
+ return CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, parentTypeBinaryName, METHOD_ID_SEPARATOR,
+ methodSelectorAndDescriptor);
+ }
+
+ public static char[] getMethodId(char[] parentTypeBinaryName, char[] methodSelector, char[] methodDescriptor) {
+ return CharArrayUtils.concat(FIELD_DESCRIPTOR_PREFIX, parentTypeBinaryName, METHOD_ID_SEPARATOR, methodSelector,
+ methodDescriptor);
+ }
+
+ /**
+ * Given a field descriptor, if the field descriptor points to a class this returns the binary name of the class. If
+ * the field descriptor points to any other type, this returns the empty string. The field descriptor may optionally
+ * contain a trailing ';'.
+ *
+ * @param fieldDescriptor
+ * @return ""
+ */
+ public static char[] fieldDescriptorToBinaryName(char[] fieldDescriptor) {
+ if (CharArrayUtils.startsWith(fieldDescriptor, 'L')) {
+ int end = fieldDescriptor.length - 1;
+ return CharArrayUtils.subarray(fieldDescriptor, 1, end);
+ }
+ return CharArrayUtils.EMPTY_CHAR_ARRAY;
+ }
+
+ /**
+ * Given a simple name, this returns the source name for the type. Note that this won't work for classes that
+ * contain a $ in their source name.
+ */
+ public static char[] simpleNameToSourceName(char[] chars) {
+ int lastSlash = CharOperation.lastIndexOf('/', chars);
+ int lastDollar = CharOperation.lastIndexOf('$', chars);
+ int lastDot = CharOperation.lastIndexOf('.', chars);
+ int startPosition = Math.max(Math.max(lastSlash, lastDollar), lastDot) + 1;
+ while (startPosition < chars.length && Character.isDigit(chars[startPosition])) {
+ startPosition++;
+ }
+ return CharArrayUtils.subarray(chars, startPosition);
+ }
+
+ /**
+ * Returns true iff the given method selector is a constructor.
+ */
+ public static boolean isConstructor(char[] selector) {
+ return selector[0] == '<' && selector.length == 6; // Can only match <init>
+ }
+
+ /**
+ * Returns true iff the given method selector is clinit.
+ */
+ public static boolean isClinit(char[] selector) {
+ return selector[0] == '<' && selector.length == 8; // Can only match <clinit>
+ }
+
+ public static String classFilePathToBinaryName(String classFilePath) {
+ if (classFilePath.endsWith(".class")) { //$NON-NLS-1$
+ return classFilePath.substring(0, classFilePath.length() - 6);
+ }
+ return classFilePath;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java
new file mode 100644
index 000000000..9715c100e
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotation.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotation extends NdNode {
+ public static final FieldManyToOne<NdTypeSignature> ANNOTATION_TYPE;
+ public static final FieldOneToMany<NdAnnotationValuePair> ELEMENT_VALUE_PAIRS;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdAnnotation> type;
+
+ static {
+ type = StructDef.create(NdAnnotation.class, NdNode.type);
+ ANNOTATION_TYPE = FieldManyToOne.create(type, NdTypeSignature.ANNOTATIONS_OF_THIS_TYPE);
+ ELEMENT_VALUE_PAIRS = FieldOneToMany.create(type, NdAnnotationValuePair.APPLIES_TO);
+ type.done();
+ }
+
+ public NdAnnotation(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdAnnotation(Nd nd) {
+ super(nd);
+ }
+
+ public NdTypeSignature getType() {
+ return ANNOTATION_TYPE.get(getNd(), this.address);
+ }
+
+ public void setType(NdTypeSignature type) {
+ ANNOTATION_TYPE.put(getNd(), this.address, type);
+ }
+
+ public List<NdAnnotationValuePair> getElementValuePairs() {
+ return ELEMENT_VALUE_PAIRS.asList(getNd(), this.address);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java
new file mode 100644
index 000000000..2328a49e8
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInConstant.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInConstant extends NdAnnotation {
+ public static final FieldOneToOne<NdConstantAnnotation> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdAnnotationInConstant> type;
+
+ static {
+ type = StructDef.create(NdAnnotationInConstant.class, NdAnnotation.type);
+ OWNER = FieldOneToOne.createOwner(type, NdConstantAnnotation.class, NdConstantAnnotation.VALUE);
+ type.done();
+ }
+
+ public NdAnnotationInConstant(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdAnnotationInConstant(Nd nd) {
+ super(nd);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java
new file mode 100644
index 000000000..e7e48ebc8
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethod.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInMethod extends NdAnnotation {
+ public static final FieldManyToOne<NdMethod> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdAnnotationInMethod> type;
+
+ static {
+ type = StructDef.create(NdAnnotationInMethod.class, NdAnnotation.type);
+ OWNER = FieldManyToOne.createOwner(type, NdMethod.ANNOTATIONS);
+ type.done();
+ }
+
+ public NdAnnotationInMethod(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdAnnotationInMethod(Nd nd, NdMethod owner) {
+ super(nd);
+
+ OWNER.put(getNd(), this.address, owner);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java
new file mode 100644
index 000000000..0a4f3fb68
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInMethodParameter.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInMethodParameter extends NdAnnotation {
+ public static final FieldManyToOne<NdMethodParameter> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdAnnotationInMethodParameter> type;
+
+ static {
+ type = StructDef.create(NdAnnotationInMethodParameter.class, NdAnnotation.type);
+ OWNER = FieldManyToOne.createOwner(type, NdMethodParameter.ANNOTATIONS);
+ type.done();
+ }
+
+ public NdAnnotationInMethodParameter(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdAnnotationInMethodParameter(Nd nd, NdMethodParameter owner) {
+ super(nd);
+
+ OWNER.put(getNd(), this.address, owner);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java
new file mode 100644
index 000000000..c220ed9f0
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInType.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInType extends NdAnnotation {
+ public static final FieldManyToOne<NdType> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdAnnotationInType> type;
+
+ static {
+ type = StructDef.create(NdAnnotationInType.class, NdAnnotation.type);
+ OWNER = FieldManyToOne.createOwner(type, NdType.ANNOTATIONS);
+ type.done();
+ }
+
+ public NdAnnotationInType(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdAnnotationInType(Nd nd, NdType owner) {
+ super(nd);
+
+ OWNER.put(getNd(), this.address, owner);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java
new file mode 100644
index 000000000..378b2d44a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationInVariable.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationInVariable extends NdAnnotation {
+ public static final FieldManyToOne<NdVariable> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdAnnotationInVariable> type;
+
+ static {
+ type = StructDef.create(NdAnnotationInVariable.class, NdAnnotation.type);
+ OWNER = FieldManyToOne.createOwner(type, NdVariable.ANNOTATIONS);
+ type.done();
+ }
+
+ public NdAnnotationInVariable(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdAnnotationInVariable(Nd nd, NdVariable owner) {
+ super(nd);
+
+ OWNER.put(getNd(), this.address, owner);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java
new file mode 100644
index 000000000..f62ceb37b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdAnnotationValuePair.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdAnnotationValuePair extends NdNode {
+ public static final FieldManyToOne<NdAnnotation> APPLIES_TO;
+ public static final FieldString NAME;
+ public static final FieldOneToOne<NdConstant> VALUE;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdAnnotationValuePair> type;
+
+ static {
+ type = StructDef.create(NdAnnotationValuePair.class, NdNode.type);
+ APPLIES_TO = FieldManyToOne.createOwner(type, NdAnnotation.ELEMENT_VALUE_PAIRS);
+ NAME = type.addString();
+ VALUE = FieldOneToOne.create(type, NdConstant.class, NdConstant.PARENT_ANNOTATION_VALUE);
+ type.done();
+ }
+
+ public NdAnnotationValuePair(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdAnnotationValuePair(NdAnnotation annotation, char[] name) {
+ super(annotation.getNd());
+ Nd nd = annotation.getNd();
+ APPLIES_TO.put(nd, this.address, annotation);
+ NAME.put(nd, this.address, name);
+ }
+
+ public NdAnnotation getAnnotation() {
+ return APPLIES_TO.get(getNd(), this.address);
+ }
+
+ public IString getName() {
+ return NAME.get(getNd(), this.address);
+ }
+
+ public void setName(String name) {
+ NAME.put(getNd(), this.address, name);
+ }
+
+ /**
+ * Returns the value of this annotation or null if none
+ */
+ public NdConstant getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ public void setValue(NdConstant value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
new file mode 100644
index 000000000..25938a102
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdBinding.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jdt.core.Flags;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Base class for bindings in the {@link Nd}.
+ */
+public abstract class NdBinding extends NdNode implements IAdaptable {
+ public static final FieldInt MODIFIERS;
+ public static final FieldOneToMany<NdTypeParameter> TYPE_PARAMETERS;
+ public static final FieldManyToOne<NdResourceFile> FILE;
+ public static final FieldOneToMany<NdVariable> VARIABLES;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdBinding> type;
+
+ static {
+ type = StructDef.create(NdBinding.class, NdNode.type);
+ MODIFIERS = type.addInt();
+ TYPE_PARAMETERS = FieldOneToMany.create(type, NdTypeParameter.PARENT);
+ FILE = FieldManyToOne.createOwner(type, NdResourceFile.ALL_NODES);
+ VARIABLES = FieldOneToMany.create(type, NdVariable.PARENT);
+ type.done();
+ }
+
+ public NdBinding(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdBinding(Nd nd, NdResourceFile resource) {
+ super(nd);
+
+ FILE.put(nd, this.address, resource);
+ }
+
+ public List<NdVariable> getVariables() {
+ return VARIABLES.asList(getNd(), this.address);
+ }
+
+ /**
+ * Tests whether this binding has one of the flags defined in {@link Flags}
+ */
+ public boolean hasModifier(int toTest) {
+ return (MODIFIERS.get(getNd(), this.address) & toTest) != 0;
+ }
+
+ /**
+ * Sets the modifiers for this binding (defined in {@link Flags})
+ */
+ public void setModifiers(int toSet) {
+ MODIFIERS.put(getNd(), this.address, toSet);
+ }
+
+ public int getModifiers() {
+ return MODIFIERS.get(getNd(), this.address);
+ }
+
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Object getAdapter(Class adapter) {
+ if (adapter.isAssignableFrom(NdBinding.class))
+ return this;
+
+ return null;
+ }
+
+ public final int getBindingConstant() {
+ return getNodeType();
+ }
+
+ public void setFile(NdResourceFile file) {
+ FILE.put(getNd(), this.address, file);
+ }
+
+ public NdResourceFile getFile() {
+ return FILE.get(getNd(), this.address);
+ }
+
+ public char[][] getTypeParameterSignatures() {
+ List<NdTypeParameter> parameters = getTypeParameters();
+ char[][] result = new char[parameters.size()][];
+
+ int idx = 0;
+ for (NdTypeParameter next : parameters) {
+ char[] nextContents = getSignatureFor(next);
+ result[idx] = nextContents;
+ idx++;
+ }
+ return result;
+ }
+
+ private char[] getSignatureFor(NdTypeParameter next) {
+ CharArrayBuffer nextArray = new CharArrayBuffer();
+ next.getSignature(nextArray);
+ char[] nextContents = nextArray.getContents();
+ return nextContents;
+ }
+
+ public List<NdTypeParameter> getTypeParameters() {
+ return TYPE_PARAMETERS.asList(getNd(), this.address);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdComplexTypeSignature.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdComplexTypeSignature.java
new file mode 100644
index 000000000..b348f4ea7
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdComplexTypeSignature.java
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Represents a type signature that is anything other than a trivial reference to a concrete
+ * type. If a type reference includes annotations, generic arguments, wildcards, or is a
+ * type variable, this object represents it.
+ * <p>
+ * Arrays are encoded in a special way. The RAW_TYPE points to a sentinel type called '['
+ * and the first type argument holds the array type.
+ */
+public class NdComplexTypeSignature extends NdTypeSignature {
+ public static final FieldString VARIABLE_IDENTIFIER;
+ public static final FieldManyToOne<NdTypeId> RAW_TYPE;
+ public static final FieldOneToMany<NdTypeArgument> TYPE_ARGUMENTS;
+ public static final FieldManyToOne<NdComplexTypeSignature> DECLARING_TYPE;
+ public static final FieldOneToMany<NdComplexTypeSignature> DECLARED_TYPES;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdComplexTypeSignature> type;
+
+ static {
+ type = StructDef.create(NdComplexTypeSignature.class, NdTypeSignature.type);
+ VARIABLE_IDENTIFIER = type.addString();
+ RAW_TYPE = FieldManyToOne.create(type, NdTypeId.USED_AS_COMPLEX_TYPE);
+ TYPE_ARGUMENTS = FieldOneToMany.create(type, NdTypeArgument.PARENT);
+ DECLARING_TYPE = FieldManyToOne.create(type, null);
+ DECLARED_TYPES = FieldOneToMany.create(type, DECLARING_TYPE);
+
+ type.useStandardRefCounting().done();
+ }
+
+ public NdComplexTypeSignature(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdComplexTypeSignature(Nd nd) {
+ super(nd);
+ }
+
+ @Override
+ public NdTypeId getRawType() {
+ return RAW_TYPE.get(getNd(), this.address);
+ }
+
+ public void setVariableIdentifier(char[] variableIdentifier) {
+ VARIABLE_IDENTIFIER.put(getNd(), this.address, variableIdentifier);
+ }
+
+ /**
+ * If this type is a type variable, this returns the variable's identifier.
+ */
+ public IString getVariableIdentifier() {
+ return VARIABLE_IDENTIFIER.get(getNd(), this.address);
+ }
+
+ public void setRawType(NdTypeId rawType) {
+ RAW_TYPE.put(getNd(), this.address, rawType);
+ }
+
+ public void setGenericDeclaringType(NdComplexTypeSignature enclosingType) {
+ DECLARING_TYPE.put(getNd(), this.address, enclosingType);
+ }
+
+ /**
+ * Returns the declaring type (as reported by the type's generic signature).
+ * Not to be confused with the declaring type as stored in the class file.
+ * That is stored in {@link NdType#getDeclaringType}. Any class that is
+ * nested inside another class with generic arguments will have one of
+ * these. Classes nested inside non-generic classes won't have one of these,
+ * and neither will non-nested classes.
+ */
+ public NdComplexTypeSignature getGenericDeclaringType() {
+ return DECLARING_TYPE.get(getNd(), this.address);
+ }
+
+ @Override
+ public List<NdTypeArgument> getTypeArguments() {
+ return TYPE_ARGUMENTS.asList(getNd(), this.address);
+ }
+
+ @Override
+ public NdTypeSignature getArrayDimensionType() {
+ if (isArrayType()) {
+ long size = TYPE_ARGUMENTS.size(getNd(), this.address);
+
+ if (size != 1) {
+ throw new IndexException("Array types should have exactly one argument"); //$NON-NLS-1$
+ }
+
+ return TYPE_ARGUMENTS.get(getNd(), this.address, 0).getType();
+ }
+ return null;
+ }
+
+ @Override
+ public void getSignature(CharArrayBuffer result, boolean includeTrailingSemicolon) {
+ NdComplexTypeSignature parentSignature = getGenericDeclaringType();
+
+ if (isTypeVariable()) {
+ result.append('T');
+ result.append(getVariableIdentifier().getChars());
+ if (includeTrailingSemicolon) {
+ result.append(';');
+ }
+ return;
+ }
+
+ NdTypeSignature arrayDimension = getArrayDimensionType();
+ if (arrayDimension != null) {
+ result.append('[');
+ arrayDimension.getSignature(result);
+ return;
+ }
+ if (parentSignature != null) {
+ parentSignature.getSignature(result, false);
+ result.append('.');
+ char[] simpleName = getRawType().getSimpleName().getChars();
+ result.append(simpleName);
+ } else {
+ result.append(getRawType().getFieldDescriptorWithoutTrailingSemicolon());
+ }
+
+ List<NdTypeArgument> arguments = getTypeArguments();
+ if (!arguments.isEmpty()) {
+ result.append('<');
+ for (NdTypeArgument next : arguments) {
+ next.getSignature(result);
+ }
+ result.append('>');
+ }
+ if (includeTrailingSemicolon) {
+ result.append(';');
+ }
+ }
+
+ @Override
+ public boolean isTypeVariable() {
+ return getVariableIdentifier().length() != 0;
+ }
+
+ @Override
+ public List<NdTypeSignature> getDeclaringTypeChain() {
+ NdComplexTypeSignature declaringType = getGenericDeclaringType();
+
+ if (declaringType == null) {
+ return Collections.singletonList((NdTypeSignature)this);
+ }
+
+ List<NdTypeSignature> result = new ArrayList<>();
+ computeDeclaringTypes(result);
+ return result;
+ }
+
+ private void computeDeclaringTypes(List<NdTypeSignature> result) {
+ NdComplexTypeSignature declaringType = getGenericDeclaringType();
+
+ if (declaringType != null) {
+ declaringType.computeDeclaringTypes(result);
+ }
+
+ result.add(this);
+ }
+
+ @Override
+ public boolean isArrayType() {
+ NdTypeId rawType = getRawType();
+
+ if (rawType == null) {
+ return false;
+ }
+
+ if (rawType.getFieldDescriptor().comparePrefix(JavaNames.ARRAY_FIELD_DESCRIPTOR_PREFIX, true) == 0) { // $NON-NLS-1$
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstant.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstant.java
new file mode 100644
index 000000000..96e604588
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstant.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public abstract class NdConstant extends NdNode {
+ // Parent pointers. Only one will be non-null.
+ // TODO(sxenos): Create something like a union to hold these, to eliminate this
+ // sparse data
+ public static final FieldManyToOne<NdConstantArray> PARENT_ARRAY;
+ public static final FieldOneToOne<NdAnnotationValuePair> PARENT_ANNOTATION_VALUE;
+ public static final FieldOneToOne<NdVariable> PARENT_VARIABLE;
+ public static final FieldOneToOne<NdMethod> PARENT_METHOD;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstant> type;
+
+ static {
+ type = StructDef.createAbstract(NdConstant.class, NdNode.type);
+ PARENT_ARRAY = FieldManyToOne.createOwner(type, NdConstantArray.ELEMENTS);
+ PARENT_ANNOTATION_VALUE = FieldOneToOne.createOwner(type, NdAnnotationValuePair.class,
+ NdAnnotationValuePair.VALUE);
+ PARENT_VARIABLE = FieldOneToOne.createOwner(type, NdVariable.class, NdVariable.CONSTANT);
+ PARENT_METHOD = FieldOneToOne.createOwner(type, NdMethod.class, NdMethod.DEFAULT_VALUE);
+ type.done();
+ }
+
+ public NdConstant(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstant(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstant create(Nd nd, Constant constant) {
+ if (constant == Constant.NotAConstant) {
+ return null;
+ }
+
+ switch (constant.typeID()) {
+ case TypeIds.T_boolean:
+ return NdConstantBoolean.create(nd, constant.booleanValue());
+ case TypeIds.T_byte:
+ return NdConstantByte.create(nd, constant.byteValue());
+ case TypeIds.T_char:
+ return NdConstantChar.create(nd, constant.charValue());
+ case TypeIds.T_double:
+ return NdConstantDouble.create(nd, constant.doubleValue());
+ case TypeIds.T_float:
+ return NdConstantFloat.create(nd, constant.floatValue());
+ case TypeIds.T_int:
+ return NdConstantInt.create(nd, constant.intValue());
+ case TypeIds.T_long:
+ return NdConstantLong.create(nd, constant.longValue());
+ case TypeIds.T_short:
+ return NdConstantShort.create(nd, constant.shortValue());
+ case TypeIds.T_JavaLangString:
+ return NdConstantString.create(nd, constant.stringValue());
+ default:
+ throw new IllegalArgumentException("Unknown typeID() " + constant.typeID()); //$NON-NLS-1$
+ }
+ }
+
+ public void setParent(NdConstantArray result) {
+ PARENT_ARRAY.put(getNd(), this.address, result);
+ }
+
+ /**
+ * Returns the {@link Constant} corresponding to the value of this {@link NdConstant} or null if the receiver
+ * corresponds to a {@link Constant}.
+ */
+ public abstract Constant getConstant();
+
+ public String toString() {
+ try {
+ return getConstant().toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java
new file mode 100644
index 000000000..a69e52c4c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantAnnotation.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantAnnotation extends NdConstant {
+ public static final FieldOneToOne<NdAnnotationInConstant> VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantAnnotation> type;
+
+ static {
+ type = StructDef.create(NdConstantAnnotation.class, NdConstant.type);
+ VALUE = FieldOneToOne.create(type, NdAnnotationInConstant.class, NdAnnotationInConstant.OWNER);
+ type.done();
+ }
+
+ public NdConstantAnnotation(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantAnnotation(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantAnnotation create(Nd nd, NdAnnotationInConstant value) {
+ NdConstantAnnotation result = new NdConstantAnnotation(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(NdAnnotationInConstant value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public NdAnnotation getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return null;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantArray.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantArray.java
new file mode 100644
index 000000000..b9833ce02
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantArray.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantArray extends NdConstant {
+ public static final FieldOneToMany<NdConstant> ELEMENTS;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantArray> type;
+
+ static {
+ type = StructDef.create(NdConstantArray.class, NdConstant.type);
+ ELEMENTS = FieldOneToMany.create(type, NdConstant.PARENT_ARRAY, 2);
+ type.done();
+ }
+
+ public NdConstantArray(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdConstantArray(Nd nd) {
+ super(nd);
+ }
+
+ public List<NdConstant> getValue() {
+ return ELEMENTS.asList(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return null;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantBoolean.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantBoolean.java
new file mode 100644
index 000000000..564f7a79b
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantBoolean.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantBoolean extends NdConstant {
+ public static final FieldByte VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantBoolean> type;
+
+ static {
+ type = StructDef.create(NdConstantBoolean.class, NdConstant.type);
+ VALUE = type.addByte();
+ type.done();
+ }
+
+ public NdConstantBoolean(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantBoolean(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantBoolean create(Nd nd, boolean value) {
+ NdConstantBoolean result = new NdConstantBoolean(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(boolean value) {
+ VALUE.put(getNd(), this.address, value ? (byte)1 : (byte)0);
+ }
+
+ public boolean getValue() {
+ return VALUE.get(getNd(), this.address) != 0;
+ }
+
+ @Override
+ public Constant getConstant() {
+ return BooleanConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantByte.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantByte.java
new file mode 100644
index 000000000..77c99ec56
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantByte.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.ByteConstant;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantByte extends NdConstant {
+ public static final FieldByte VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantByte> type;
+
+ static {
+ type = StructDef.create(NdConstantByte.class, NdConstant.type);
+ VALUE = type.addByte();
+ type.done();
+ }
+
+ public NdConstantByte(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantByte(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantByte create(Nd nd, byte value) {
+ NdConstantByte result = new NdConstantByte(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(byte value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public byte getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return ByteConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantChar.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantChar.java
new file mode 100644
index 000000000..f54975add
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantChar.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.CharConstant;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldChar;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantChar extends NdConstant {
+ public static final FieldChar VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantChar> type;
+
+ static {
+ type = StructDef.create(NdConstantChar.class, NdConstant.type);
+ VALUE = type.addChar();
+ type.done();
+ }
+
+ public NdConstantChar(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantChar(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantChar create(Nd nd, char value) {
+ NdConstantChar result = new NdConstantChar(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(char value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public char getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return CharConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantClass.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantClass.java
new file mode 100644
index 000000000..fc6fc6c90
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantClass.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantClass extends NdConstant {
+ public static final FieldManyToOne<NdTypeSignature> VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantClass> type;
+
+ static {
+ type = StructDef.create(NdConstantClass.class, NdConstant.type);
+ VALUE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_CONSTANT);
+ type.done();
+ }
+
+ public NdConstantClass(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantClass(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantClass create(Nd nd, NdTypeSignature value) {
+ NdConstantClass result = new NdConstantClass(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(NdTypeSignature value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public NdTypeSignature getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return null;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantDouble.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantDouble.java
new file mode 100644
index 000000000..56354ba0a
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantDouble.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.DoubleConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldDouble;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantDouble extends NdConstant {
+ public static final FieldDouble VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantDouble> type;
+
+ static {
+ type = StructDef.create(NdConstantDouble.class, NdConstant.type);
+ VALUE = type.addDouble();
+ type.done();
+ }
+
+ public NdConstantDouble(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantDouble(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantDouble create(Nd nd, double value) {
+ NdConstantDouble result = new NdConstantDouble(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(double value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public double getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return DoubleConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantEnum.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantEnum.java
new file mode 100644
index 000000000..c4e9c8e99
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantEnum.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantEnum extends NdConstant {
+ public static final FieldManyToOne<NdTypeSignature> ENUM_TYPE;
+ public static final FieldString ENUM_VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantEnum> type;
+
+ static {
+ type = StructDef.create(NdConstantEnum.class, NdConstant.type);
+ ENUM_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_ENUM_CONSTANT);
+ ENUM_VALUE = type.addString();
+ type.done();
+ }
+
+ public NdConstantEnum(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantEnum(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantEnum create(NdTypeSignature enumType, String enumValue) {
+ NdConstantEnum result = new NdConstantEnum(enumType.getNd());
+ result.setEnumType(enumType);
+ result.setEnumValue(enumValue);
+ return result;
+ }
+
+ public void setEnumType(NdTypeSignature enumType) {
+ ENUM_TYPE.put(getNd(), this.address, enumType);
+ }
+
+ public void setEnumValue(String enumType) {
+ ENUM_VALUE.put(getNd(), this.address, enumType);
+ }
+
+ public NdTypeSignature getType() {
+ return ENUM_TYPE.get(getNd(), this.address);
+ }
+
+ public char[] getValue() {
+ return ENUM_VALUE.get(getNd(), this.address).getChars();
+ }
+
+ @Override
+ public Constant getConstant() {
+ return null;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantFloat.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantFloat.java
new file mode 100644
index 000000000..731405eeb
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantFloat.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.FloatConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldFloat;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantFloat extends NdConstant {
+ public static final FieldFloat VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantFloat> type;
+
+ static {
+ type = StructDef.create(NdConstantFloat.class, NdConstant.type);
+ VALUE = type.addFloat();
+ type.done();
+ }
+
+ public NdConstantFloat(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantFloat(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantFloat create(Nd nd, float value) {
+ NdConstantFloat result = new NdConstantFloat(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(float value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public float getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return FloatConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantInt.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantInt.java
new file mode 100644
index 000000000..f66367500
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantInt.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.IntConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantInt extends NdConstant {
+ public static final FieldInt VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantInt> type;
+
+ static {
+ type = StructDef.create(NdConstantInt.class, NdConstant.type);
+ VALUE = type.addInt();
+ type.done();
+ }
+
+ public NdConstantInt(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantInt(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantInt create(Nd nd, int value) {
+ NdConstantInt result = new NdConstantInt(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(int value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public int getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return IntConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantLong.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantLong.java
new file mode 100644
index 000000000..9bed54698
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantLong.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.LongConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantLong extends NdConstant {
+ public static final FieldLong VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantLong> type;
+
+ static {
+ type = StructDef.create(NdConstantLong.class, NdConstant.type);
+ VALUE = type.addLong();
+ type.done();
+ }
+
+ public NdConstantLong(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantLong(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantLong create(Nd nd, long value) {
+ NdConstantLong result = new NdConstantLong(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(long value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public long getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return LongConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantShort.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantShort.java
new file mode 100644
index 000000000..3c9fa0f8f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantShort.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.ShortConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldShort;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantShort extends NdConstant {
+ public static final FieldShort VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantShort> type;
+
+ static {
+ type = StructDef.create(NdConstantShort.class, NdConstant.type);
+ VALUE = type.addShort();
+ type.done();
+ }
+
+ public NdConstantShort(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantShort(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantShort create(Nd nd, short value) {
+ NdConstantShort result = new NdConstantShort(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(short value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public short getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return ShortConstant.fromValue(getValue());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantString.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantString.java
new file mode 100644
index 000000000..4c1aeffbc
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdConstantString.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.impl.StringConstant;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public final class NdConstantString extends NdConstant {
+ public static final FieldString VALUE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdConstantString> type;
+
+ static {
+ type = StructDef.create(NdConstantString.class, NdConstant.type);
+ VALUE = type.addString();
+ type.done();
+ }
+
+ public NdConstantString(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdConstantString(Nd nd) {
+ super(nd);
+ }
+
+ public static NdConstantString create(Nd nd, String value) {
+ NdConstantString result = new NdConstantString(nd);
+ result.setValue(value);
+ return result;
+ }
+
+ public void setValue(String value) {
+ VALUE.put(getNd(), this.address, value);
+ }
+
+ public IString getValue() {
+ return VALUE.get(getNd(), this.address);
+ }
+
+ @Override
+ public Constant getConstant() {
+ return StringConstant.fromValue(getValue().getString());
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
new file mode 100644
index 000000000..77991be21
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethod.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldShort;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdMethod extends NdBinding {
+ public static final FieldManyToOne<NdMethodId> METHOD_ID;
+ public static final FieldShort METHOD_FLAGS;
+ public static final FieldManyToOne<NdType> PARENT;
+ public static final FieldOneToMany<NdVariable> DECLARED_VARIABLES;
+ public static final FieldOneToMany<NdMethodParameter> PARAMETERS;
+ public static final FieldOneToOne<NdConstant> DEFAULT_VALUE;
+ public static final FieldOneToMany<NdMethodException> EXCEPTIONS;
+ public static final FieldManyToOne<NdTypeSignature> RETURN_TYPE;
+ public static final FieldLong TAG_BITS;
+ public static final FieldOneToMany<NdAnnotationInMethod> ANNOTATIONS;
+ public static final FieldOneToMany<NdTypeAnnotationInMethod> TYPE_ANNOTATIONS;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdMethod> type;
+
+ static {
+ type = StructDef.create(NdMethod.class, NdBinding.type);
+ METHOD_ID = FieldManyToOne.create(type, NdMethodId.METHODS);
+ METHOD_FLAGS = type.addShort();
+ PARENT = FieldManyToOne.create(type, NdType.METHODS);
+ PARAMETERS = FieldOneToMany.create(type, NdMethodParameter.PARENT);
+ DECLARED_VARIABLES = FieldOneToMany.create(type, NdVariable.DECLARING_METHOD);
+ DEFAULT_VALUE = FieldOneToOne.create(type, NdConstant.class, NdConstant.PARENT_METHOD);
+ EXCEPTIONS = FieldOneToMany.create(type, NdMethodException.PARENT);
+ RETURN_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_RETURN_TYPE);
+ TAG_BITS = type.addLong();
+ ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInMethod.OWNER);
+ TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInMethod.OWNER);
+ type.done();
+ }
+
+ public static final byte FLG_GENERIC_SIGNATURE_PRESENT = 0x0001;
+ public static final byte FLG_THROWS_SIGNATURE_PRESENT = 0x0002;
+
+ public NdMethod(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdMethod(NdType parent) {
+ super(parent.getNd(), parent.getFile());
+
+ PARENT.put(getNd(), this.address, parent);
+ }
+
+ public NdMethodId getMethodId() {
+ return METHOD_ID.get(getNd(), this.address);
+ }
+
+ /**
+ * Returns method parameter names that were not defined by the compiler.
+ */
+ public char[][] getParameterNames() {
+ List<NdMethodParameter> params = getMethodParameters();
+
+ // Use index to count the "real" parameters.
+ int index = 0;
+ char[][] result = new char[params.size()][];
+ for (int idx = 0; idx < result.length; idx++) {
+ NdMethodParameter param = params.get(idx);
+ if (!param.isCompilerDefined()) {
+ result[index] = param.getName().getChars();
+ index++;
+ }
+ }
+ return CharArrayUtils.subarray(result, 0, index);
+ }
+
+ public List<NdMethodParameter> getMethodParameters() {
+ return PARAMETERS.asList(getNd(), this.address);
+ }
+
+ public List<NdAnnotationInMethod> getAnnotations() {
+ return ANNOTATIONS.asList(getNd(), this.address);
+ }
+
+ public void setDefaultValue(NdConstant value) {
+ DEFAULT_VALUE.put(getNd(), this.address, value);
+ }
+
+ public NdConstant getDefaultValue() {
+ return DEFAULT_VALUE.get(getNd(), this.address);
+ }
+
+ public void setReturnType(NdTypeSignature createTypeSignature) {
+ RETURN_TYPE.put(getNd(), this.address, createTypeSignature);
+ }
+
+ public void setMethodId(NdMethodId methodId) {
+ METHOD_ID.put(getNd(), this.address, methodId);
+ }
+
+ public List<NdTypeAnnotationInMethod> getTypeAnnotations() {
+ return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+ }
+
+ public List<NdMethodException> getExceptions() {
+ return EXCEPTIONS.asList(getNd(), this.address);
+ }
+
+ /**
+ * Returns the return type for this method or null if the method returns void
+ */
+ public NdTypeSignature getReturnType() {
+ return RETURN_TYPE.get(getNd(), this.address);
+ }
+
+ public int getFlags() {
+ return METHOD_FLAGS.get(getNd(), this.address);
+ }
+
+ public boolean hasAllFlags(int flags) {
+ int ourFlags = getFlags();
+
+ return (ourFlags & flags) == flags;
+ }
+
+ public void setFlags(int flags) {
+ METHOD_FLAGS.put(getNd(), this.address, (short) (getFlags() | flags));
+ }
+
+ public void setTagBits(long bits) {
+ TAG_BITS.put(getNd(), this.address, bits);
+ }
+
+ public long getTagBits() {
+ return TAG_BITS.get(getNd(), this.address);
+ }
+
+ public String toString() {
+ try {
+ CharArrayBuffer arrayBuffer = new CharArrayBuffer();
+ arrayBuffer.append(getMethodId().getSelector());
+ getGenericSignature(arrayBuffer, true);
+ return arrayBuffer.toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+
+ public void getGenericSignature(CharArrayBuffer result, boolean includeExceptions) {
+ NdTypeParameter.getSignature(result, getTypeParameters());
+
+ result.append('(');
+ for (NdMethodParameter next : getMethodParameters()) {
+ // Compiler-defined arguments don't show up in the generic signature
+ if (!next.isCompilerDefined()) {
+ next.getType().getSignature(result);
+ }
+ }
+ result.append(')');
+ NdTypeSignature returnType = getReturnType();
+ if (returnType == null) {
+ result.append('V');
+ } else {
+ returnType.getSignature(result);
+ }
+ if (includeExceptions) {
+ List<NdMethodException> exceptions = getExceptions();
+ for (NdMethodException next : exceptions) {
+ result.append('^');
+ next.getExceptionType().getSignature(result);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java
new file mode 100644
index 000000000..4c7cfe5a2
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodException.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdMethodException extends NdNode {
+
+ public static final FieldManyToOne<NdMethod> PARENT;
+ public static final FieldManyToOne<NdTypeSignature> EXCEPTION_TYPE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdMethodException> type;
+
+ static {
+ type = StructDef.create(NdMethodException.class, NdNode.type);
+ PARENT = FieldManyToOne.createOwner(type, NdMethod.EXCEPTIONS);
+ EXCEPTION_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_EXCEPTION);
+ type.done();
+ }
+
+ public NdMethodException(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdMethodException(NdMethod method, NdTypeSignature createTypeSignature) {
+ super(method.getNd());
+
+ PARENT.put(getNd(), this.address, method);
+ EXCEPTION_TYPE.put(getNd(), this.address, createTypeSignature);
+ }
+
+ public NdTypeSignature getExceptionType() {
+ return EXCEPTION_TYPE.get(getNd(), this.address);
+ }
+
+ public NdMethod getParent() {
+ return PARENT.get(getNd(), this.address);
+ }
+
+ public String toString() {
+ try {
+ return getExceptionType().toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodId.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodId.java
new file mode 100644
index 000000000..f554b8003
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodId.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * Represents the fully-qualified signature a method. Holds back-pointers to all the entities that refer to the name,
+ * along with pointers to all methods that have this fully-qualified name. Note that this isn't the class declaration
+ * itself. If there are multiple jar files containing a class of the same fully-qualified name, there may also be
+ * multiple methods with the same method ID.
+ */
+public class NdMethodId extends NdNode {
+ public static final FieldSearchKey<JavaIndex> METHOD_NAME;
+ public static final FieldOneToMany<NdMethod> METHODS;
+ public static final FieldOneToMany<NdType> DECLARED_TYPES;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdMethodId> type;
+
+ static {
+ type = StructDef.create(NdMethodId.class, NdNode.type);
+ METHOD_NAME = FieldSearchKey.create(type, JavaIndex.METHODS);
+ METHODS = FieldOneToMany.create(type, NdMethod.METHOD_ID, 2);
+ DECLARED_TYPES = FieldOneToMany.create(type, NdType.DECLARING_METHOD);
+
+ type.useStandardRefCounting().done();
+ }
+
+ public NdMethodId(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ /**
+ *
+ * @param nd
+ * @param methodIdentifier a field descriptor for the method type followed by a "#" followed by a method selector
+ * followed by method descriptor. For example, "Lorg/eclipse/MyClass#foo()Ljava/lang/Object;V"
+ */
+ public NdMethodId(Nd nd, char[] methodIdentifier) {
+ super(nd);
+
+ METHOD_NAME.put(nd, this.address, methodIdentifier);
+ }
+
+ public List<NdType> getDeclaredTypes() {
+ return DECLARED_TYPES.asList(getNd(), this.address);
+ }
+
+ /**
+ * Returns the field descriptor for the type (without a trailing ';') followed by a # followed by the method
+ * selector followed by the method descriptor. For example, "Lorg/eclipse/MyClass#foo()Ljava/lang/Object;V"
+ */
+ public IString getMethodName() {
+ return METHOD_NAME.get(getNd(), this.address);
+ }
+
+ public char[] getSelector() {
+ char[] name = getMethodName().getChars();
+ int selectorStart = CharArrayUtils.indexOf('#', name) + 1;
+ int selectorEnd = CharArrayUtils.indexOf('(', name, selectorStart, name.length);
+ if (selectorEnd == -1) {
+ selectorEnd = name.length;
+ }
+ return CharArrayUtils.subarray(name, selectorStart, selectorEnd);
+ }
+
+ public boolean isConstructor() {
+ return JavaNames.isConstructor(getSelector());
+ }
+
+ public char[] getMethodDescriptor() {
+ char[] name = getMethodName().getChars();
+ int descriptorStart = CharArrayUtils.indexOf('(', name, 0, name.length);
+ return CharArrayUtils.subarray(name, descriptorStart, name.length);
+ }
+
+ public boolean isClInit() {
+ return JavaNames.isClinit(getSelector());
+ }
+
+ public String toString() {
+ try {
+ return new String(getSelector());
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java
new file mode 100644
index 000000000..bdcd8324d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdMethodParameter.java
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdMethodParameter extends NdNode {
+ public static final FieldManyToOne<NdMethod> PARENT;
+ public static final FieldManyToOne<NdTypeSignature> ARGUMENT_TYPE;
+ public static final FieldString NAME;
+ public static final FieldOneToMany<NdAnnotationInMethodParameter> ANNOTATIONS;
+ public static final FieldByte FLAGS;
+
+ private static final byte FLG_COMPILER_DEFINED = 0x01;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdMethodParameter> type;
+
+ static {
+ type = StructDef.create(NdMethodParameter.class, NdNode.type);
+ PARENT = FieldManyToOne.create(type, NdMethod.PARAMETERS);
+ ARGUMENT_TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_METHOD_ARGUMENT);
+ NAME = type.addString();
+ ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInMethodParameter.OWNER);
+ FLAGS = type.addByte();
+ type.done();
+ }
+
+ public NdMethodParameter(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdMethodParameter(NdMethod parent, NdTypeSignature argumentType) {
+ super(parent.getNd());
+
+ PARENT.put(getNd(), this.address, parent);
+ ARGUMENT_TYPE.put(getNd(), this.address, argumentType);
+ }
+
+ public NdTypeSignature getType() {
+ return ARGUMENT_TYPE.get(getNd(), this.address);
+ }
+
+ public void setName(char[] name) {
+ NAME.put(getNd(), this.address, name);
+ }
+
+ public IString getName() {
+ return NAME.get(getNd(), this.address);
+ }
+
+ public List<NdAnnotationInMethodParameter> getAnnotations() {
+ return ANNOTATIONS.asList(getNd(), this.address);
+ }
+
+ private void setFlag(byte flagConstant, boolean value) {
+ int oldFlags = FLAGS.get(getNd(), this.address);
+ int newFlags = ((oldFlags & ~flagConstant) | (value ? flagConstant : 0));
+ FLAGS.put(getNd(), this.address, (byte) newFlags);
+ }
+
+ private boolean getFlag(byte flagConstant) {
+ return (FLAGS.get(getNd(), this.address) & flagConstant) != 0;
+ }
+
+ public void setCompilerDefined(boolean isCompilerDefined) {
+ setFlag(FLG_COMPILER_DEFINED, isCompilerDefined);
+ }
+
+ public boolean isCompilerDefined() {
+ return getFlag(FLG_COMPILER_DEFINED);
+ }
+
+ public String toString() {
+ try {
+ CharArrayBuffer buf = new CharArrayBuffer();
+ buf.append(getType().toString());
+ buf.append(" "); //$NON-NLS-1$
+ buf.append(getName().toString());
+ return buf.toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
new file mode 100644
index 000000000..14e2e5362
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdResourceFile.java
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany.Visitor;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.IResultRank;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex.SearchCriteria;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Represents a source of java classes (such as a .jar or .class file).
+ */
+public class NdResourceFile extends NdTreeNode {
+ public static final FieldSearchKey<JavaIndex> FILENAME;
+ public static final FieldOneToMany<NdBinding> ALL_NODES;
+ public static final FieldLong TIME_LAST_USED;
+ public static final FieldLong TIME_LAST_SCANNED;
+ public static final FieldLong SIZE_LAST_SCANNED;
+ public static final FieldLong HASHCODE_LAST_SCANNED;
+ public static final FieldOneToMany<NdWorkspaceLocation> WORKSPACE_MAPPINGS;
+ public static final FieldString JAVA_ROOT;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdResourceFile> type;
+
+ static {
+ type = StructDef.create(NdResourceFile.class, NdTreeNode.type);
+ FILENAME = FieldSearchKey.create(type, JavaIndex.FILES);
+ ALL_NODES = FieldOneToMany.create(type, NdBinding.FILE, 16);
+ TIME_LAST_USED = type.addLong();
+ TIME_LAST_SCANNED = type.addLong();
+ SIZE_LAST_SCANNED = type.addLong();
+ HASHCODE_LAST_SCANNED = type.addLong();
+ WORKSPACE_MAPPINGS = FieldOneToMany.create(type, NdWorkspaceLocation.RESOURCE);
+ JAVA_ROOT = type.addString();
+ type.done();
+ }
+
+ public NdResourceFile(Nd dom, long address) {
+ super(dom, address);
+ }
+
+ public NdResourceFile(Nd nd) {
+ super(nd, null);
+ }
+
+ public List<NdTreeNode> getChildren() {
+ return CHILDREN.asList(this.getNd(), this.address);
+ }
+
+ /**
+ * Determines whether this file is still in the index. If a {@link NdResourceFile} instance is retained while the
+ * database lock is released and reobtained, this method should be invoked to ensure that the {@link NdResourceFile}
+ * has not been deleted in the meantime.
+ */
+ public boolean isInIndex() {
+ try {
+ Nd nd = getNd();
+ // In the common case where the resource file was deleted and the memory hasn't yet been reused,
+ // this will fail.
+ if (NODE_TYPE.get(nd, this.address) != nd.getNodeType(getClass())) {
+ return false;
+ }
+
+ char[] filename = FILENAME.get(getNd(), this.address).getChars();
+
+ NdResourceFile result = JavaIndex.FILES.findBest(nd, Database.DATA_AREA_OFFSET,
+ SearchCriteria.create(filename), new IResultRank() {
+ @Override
+ public long getRank(Nd testNd, long testAddress) {
+ if (testAddress == NdResourceFile.this.address) {
+ return 1;
+ }
+ return -1;
+ }
+ });
+
+ return (this.equals(result));
+ } catch (IndexException e) {
+ // Read errors are expected here. It's possible that the resource file has been deleted and something
+ // new was written to this address, in which case we may be reading random gibberish from the database.
+ // This is likely to cause an exception.
+ return false;
+ }
+ }
+
+ public List<IPath> getAllWorkspaceLocations() {
+ final List<IPath> result = new ArrayList<>();
+
+ WORKSPACE_MAPPINGS.accept(getNd(), this.address, new Visitor<NdWorkspaceLocation>() {
+ @Override
+ public void visit(int index, NdWorkspaceLocation toVisit) {
+ result.add(new Path(toVisit.getPath().getString()));
+ }
+ });
+
+ return result;
+ }
+
+ public IPath getFirstWorkspaceLocation() {
+ if (WORKSPACE_MAPPINGS.isEmpty(getNd(), this.address)) {
+ return Path.EMPTY;
+ }
+
+ return new Path(WORKSPACE_MAPPINGS.get(getNd(), this.address, 0).getPath().toString());
+ }
+
+ public IPath getAnyOpenWorkspaceLocation(IWorkspaceRoot root) {
+ int numMappings = WORKSPACE_MAPPINGS.size(getNd(), this.address);
+
+ for (int mapping = 0; mapping < numMappings; mapping++) {
+ NdWorkspaceLocation nextMapping = WORKSPACE_MAPPINGS.get(getNd(), this.address, mapping);
+
+ IPath nextPath = new Path(nextMapping.getPath().getString());
+ if (nextPath.isEmpty()) {
+ continue;
+ }
+
+ IProject project = root.getProject(nextPath.segment(0));
+ if (project.isOpen()) {
+ return nextPath;
+ }
+ }
+
+ return Path.EMPTY;
+ }
+
+ /**
+ * Returns a workspace path to this resource if possible and the absolute filesystem location if not.
+ */
+ public IPath getPath() {
+ IPath workspacePath = getFirstWorkspaceLocation();
+
+ if (workspacePath.isEmpty()) {
+ return new Path(getLocation().getString());
+ }
+
+ return workspacePath;
+ }
+
+ public List<NdWorkspaceLocation> getWorkspaceMappings() {
+ return WORKSPACE_MAPPINGS.asList(getNd(), this.address);
+ }
+
+ public IString getLocation() {
+ return FILENAME.get(getNd(), this.address);
+ }
+
+ public void setLocation(String filename) {
+ FILENAME.put(getNd(), this.address, filename);
+ }
+
+ public FileFingerprint getFingerprint() {
+ return new FileFingerprint(
+ getTimeLastScanned(),
+ getSizeLastScanned(),
+ getHashcodeLastScanned());
+ }
+
+ private long getHashcodeLastScanned() {
+ return HASHCODE_LAST_SCANNED.get(getNd(), this.address);
+ }
+
+ /**
+ * Returns true iff the indexer has finished writing the contents of this file to the index. Returns false if
+ * indexing may still be going on. If this returns false, readers should ignore all contents of this file.
+ *
+ * @return true iff the contents of this file are usable
+ */
+ public boolean isDoneIndexing() {
+ return getTimeLastScanned() != 0;
+ }
+
+ public long getTimeLastScanned() {
+ return TIME_LAST_SCANNED.get(getNd(), this.address);
+ }
+
+ public long getSizeLastScanned() {
+ return SIZE_LAST_SCANNED.get(getNd(), this.address);
+ }
+
+ public long getTimeLastUsed() {
+ return TIME_LAST_USED.get(getNd(), this.address);
+ }
+
+ public void setTimeLastUsed(long timeLastUsed) {
+ TIME_LAST_USED.put(getNd(), this.address, timeLastUsed);
+ }
+
+ public void setFingerprint(FileFingerprint newFingerprint) {
+ TIME_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getTime());
+ HASHCODE_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getHash());
+ SIZE_LAST_SCANNED.put(getNd(), this.address, newFingerprint.getSize());
+ }
+
+ public void setPackageFragmentRoot(char[] javaRoot) {
+ JAVA_ROOT.put(getNd(), this.address, javaRoot);
+ }
+
+ /**
+ * Returns the absolute path to the java root for this .jar or .class file. If this is a .jar file, it returns its
+ * own filename.
+ */
+ public IString getPackageFragmentRoot() {
+ IString javaRoot = JAVA_ROOT.get(getNd(), this.address);
+ if (javaRoot.length() == 0) {
+ return getLocation();
+ }
+ return javaRoot;
+ }
+
+ public void markAsInvalid() {
+ TIME_LAST_SCANNED.put(getNd(), this.address, 0);
+ }
+
+ public int getBindingCount() {
+ return ALL_NODES.size(getNd(), this.address);
+ }
+
+ public List<NdBinding> getBindings() {
+ return ALL_NODES.asList(getNd(), this.address);
+ }
+
+ public NdBinding getBinding(int index) {
+ return ALL_NODES.get(getNd(), this.address, index);
+ }
+
+ public String toString() {
+ try {
+ return FILENAME.get(getNd(), this.address).toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTreeNode.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTreeNode.java
new file mode 100644
index 000000000..d9e33db80
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTreeNode.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * {@link NdTreeNode} elements form a tree of nodes rooted at a {@link NdResourceFile}. Each node contains a list of
+ * children which it declares and has a pointer to the most specific node which declares it.
+ */
+public abstract class NdTreeNode extends NdNode {
+ public static final FieldManyToOne<NdTreeNode> PARENT;
+ public static final FieldOneToMany<NdTreeNode> CHILDREN;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTreeNode> type;
+
+ static {
+ type = StructDef.create(NdTreeNode.class, NdNode.type);
+ PARENT = FieldManyToOne.create(type, null);
+ CHILDREN = FieldOneToMany.create(type, PARENT, 16);
+ type.done();
+ }
+
+ public NdTreeNode(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ protected NdTreeNode(Nd nd, NdTreeNode parent) {
+ super(nd);
+
+ PARENT.put(nd, this.address, parent == null ? 0 : parent.address);
+ }
+
+ public int getChildrenCount() {
+ return CHILDREN.size(getNd(), this.address);
+ }
+
+ public NdTreeNode getChild(int index) {
+ return CHILDREN.get(getNd(), this.address, index);
+ }
+
+ /**
+ * Returns the closest ancestor of the given type, or null if none. Note that
+ * this looks for an exact match. It will not return subtypes of the given type.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends NdTreeNode> T getAncestorOfType(Class<T> ancestorType) {
+ long targetType = getNd().getNodeType(ancestorType);
+
+ Nd nd = getNd();
+ long current = PARENT.getAddress(nd, this.address);
+
+ while (current != 0) {
+ short currentType = NODE_TYPE.get(nd, current);
+
+ if (currentType == targetType) {
+ NdNode result = load(nd, current);
+
+ if (ancestorType.isInstance(result)) {
+ return (T) result;
+ } else {
+ throw new IndexException("The node at address " + current + //$NON-NLS-1$
+ " should have been an instance of " + ancestorType.getName() + //$NON-NLS-1$
+ " but was an instance of " + result.getClass().getName()); //$NON-NLS-1$
+ }
+ }
+
+ current = PARENT.getAddress(nd, current);
+ }
+
+ return null;
+ }
+
+ NdTreeNode getParentNode() {
+ return PARENT.get(getNd(), this.address);
+ }
+
+ public NdBinding getParentBinding() throws IndexException {
+ NdNode parent= getParentNode();
+ if (parent instanceof NdBinding) {
+ return (NdBinding) parent;
+ }
+ return null;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
new file mode 100644
index 000000000..5d2836399
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdType.java
@@ -0,0 +1,266 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.internal.core.nd.INdVisitor;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+public class NdType extends NdBinding {
+ public static final FieldManyToOne<NdTypeId> TYPENAME;
+ public static final FieldManyToOne<NdTypeSignature> SUPERCLASS;
+ public static final FieldOneToMany<NdTypeInterface> INTERFACES;
+ public static final FieldManyToOne<NdTypeId> DECLARING_TYPE;
+ public static final FieldManyToOne<NdMethodId> DECLARING_METHOD;
+ public static final FieldOneToMany<NdMethod> METHODS;
+ public static final FieldOneToMany<NdTypeAnnotationInType> TYPE_ANNOTATIONS;
+ public static final FieldOneToMany<NdAnnotationInType> ANNOTATIONS;
+ public static final FieldString MISSING_TYPE_NAMES;
+ public static final FieldString SOURCE_FILE_NAME;
+ public static final FieldString INNER_CLASS_SOURCE_NAME;
+ public static final FieldByte FLAGS;
+ public static final FieldLong TAG_BITS;
+ /**
+ * Binary name that was recorded in the .class file if different from the binary
+ * name that was determined by the .class's name and location. This is only set for
+ * .class files that have been moved to the wrong folder.
+ */
+ public static final FieldString FIELD_DESCRIPTOR_FROM_CLASS;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdType> type;
+
+ static {
+ type = StructDef.create(NdType.class, NdBinding.type);
+ TYPENAME = FieldManyToOne.create(type, NdTypeId.TYPES);
+ DECLARING_TYPE = FieldManyToOne.create(type, NdTypeId.DECLARED_TYPES);
+ INTERFACES = FieldOneToMany.create(type, NdTypeInterface.APPLIES_TO);
+ SUPERCLASS = FieldManyToOne.create(type, NdTypeSignature.SUBCLASSES);
+ DECLARING_METHOD = FieldManyToOne.create(type, NdMethodId.DECLARED_TYPES);
+ METHODS = FieldOneToMany.create(type, NdMethod.PARENT, 6);
+ TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInType.OWNER);
+ ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInType.OWNER);
+ MISSING_TYPE_NAMES = type.addString();
+ SOURCE_FILE_NAME = type.addString();
+ INNER_CLASS_SOURCE_NAME = type.addString();
+ FLAGS = type.addByte();
+ TAG_BITS = type.addLong();
+ FIELD_DESCRIPTOR_FROM_CLASS = type.addString();
+ type.done();
+ }
+
+ public static final byte FLG_TYPE_ANONYMOUS = 0x0001;
+ public static final byte FLG_TYPE_LOCAL = 0x0002;
+ public static final byte FLG_TYPE_MEMBER = 0x0004;
+ public static final byte FLG_GENERIC_SIGNATURE_PRESENT = 0x0008;
+
+ public NdType(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdType(Nd nd, NdResourceFile resource) {
+ super(nd, resource);
+ }
+
+ /**
+ * Called to populate the cache for the bindings in the class scope.
+ */
+ public void acceptUncached(INdVisitor visitor) throws CoreException {
+ super.accept(visitor);
+ }
+
+ public NdTypeId getTypeId() {
+ return TYPENAME.get(getNd(), this.address);
+ }
+
+ public void setTypeId(NdTypeId typeId) {
+ TYPENAME.put(getNd(), this.address, typeId);
+ }
+
+ /**
+ * Sets the source name for this type.
+ */
+ public void setSourceNameOverride(char[] sourceName) {
+ char[] oldSourceName = getSourceName();
+ if (!CharArrayUtils.equals(oldSourceName, sourceName)) {
+ INNER_CLASS_SOURCE_NAME.put(getNd(), this.address, sourceName);
+ }
+ }
+
+ public IString getSourceNameOverride() {
+ return INNER_CLASS_SOURCE_NAME.get(getNd(), this.address);
+ }
+
+ public long getResourceAddress() {
+ return FILE.getAddress(getNd(), this.address);
+ }
+
+ public void setSuperclass(NdTypeSignature superclassTypeName) {
+ SUPERCLASS.put(getNd(), this.address, superclassTypeName);
+ }
+
+ public NdTypeSignature getSuperclass() {
+ return SUPERCLASS.get(getNd(), this.address);
+ }
+
+ public List<NdTypeInterface> getInterfaces() {
+ return INTERFACES.asList(getNd(), this.address);
+ }
+
+ public NdResourceFile getResourceFile() {
+ return FILE.get(getNd(), this.address);
+ }
+
+ public void setDeclaringMethod(NdMethodId createMethodId) {
+ DECLARING_METHOD.put(getNd(), this.address, createMethodId);
+ }
+
+ /**
+ * @param createTypeIdFromBinaryName
+ */
+ public void setDeclaringType(NdTypeId createTypeIdFromBinaryName) {
+ DECLARING_TYPE.put(getNd(), this.address, createTypeIdFromBinaryName);
+ }
+
+ public NdTypeId getDeclaringType() {
+ return DECLARING_TYPE.get(getNd(), this.address);
+ }
+
+ /**
+ * Sets the missing type names (if any) for this class. The names are encoded in a comma-separated list.
+ */
+ public void setMissingTypeNames(char[] contents) {
+ MISSING_TYPE_NAMES.put(getNd(), this.address, contents);
+ }
+
+ /**
+ * Returns the missing type names as a comma-separated list
+ */
+ public IString getMissingTypeNames() {
+ return MISSING_TYPE_NAMES.get(getNd(), this.address);
+ }
+
+ public void setSourceFileName(char[] sourceFileName) {
+ SOURCE_FILE_NAME.put(getNd(), this.address, sourceFileName);
+ }
+
+ public IString getSourceFileName() {
+ return SOURCE_FILE_NAME.get(getNd(), this.address);
+ }
+
+ public void setAnonymous(boolean anonymous) {
+ setFlag(FLG_TYPE_ANONYMOUS, anonymous);
+ }
+
+ public void setIsLocal(boolean local) {
+ setFlag(FLG_TYPE_LOCAL, local);
+ }
+
+ public void setIsMember(boolean member) {
+ setFlag(FLG_TYPE_MEMBER, member);
+ }
+
+ public boolean isAnonymous() {
+ return getFlag(FLG_TYPE_ANONYMOUS);
+ }
+
+ public boolean isLocal() {
+ return getFlag(FLG_TYPE_LOCAL);
+ }
+
+ public boolean isMember() {
+ return getFlag(FLG_TYPE_MEMBER);
+ }
+
+ public void setFlag(byte flagConstant, boolean value) {
+ int oldFlags = FLAGS.get(getNd(), this.address);
+ int newFlags = ((oldFlags & ~flagConstant) | (value ? flagConstant : 0));
+ FLAGS.put(getNd(), this.address, (byte)newFlags);
+ }
+
+ public boolean getFlag(byte flagConstant) {
+ return (FLAGS.get(getNd(), this.address) & flagConstant) != 0;
+ }
+
+ public char[] getSourceName() {
+ IString sourceName = getSourceNameOverride();
+ if (sourceName.length() != 0) {
+ return sourceName.getChars();
+ }
+ char[] simpleName = getTypeId().getSimpleNameCharArray();
+ return JavaNames.simpleNameToSourceName(simpleName);
+ }
+
+ public NdMethodId getDeclaringMethod() {
+ return DECLARING_METHOD.get(getNd(), this.address);
+ }
+
+ @Override
+ public List<NdTypeParameter> getTypeParameters() {
+ return TYPE_PARAMETERS.asList(getNd(), this.address);
+ }
+
+ public List<NdTypeAnnotationInType> getTypeAnnotations() {
+ return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+ }
+
+ public List<NdAnnotationInType> getAnnotations() {
+ return ANNOTATIONS.asList(getNd(), this.address);
+ }
+
+ public List<NdMethod> getMethods() {
+ return METHODS.asList(getNd(), this.address);
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return "class " + new String(getSourceName()); //$NON-NLS-1$
+ } catch (RuntimeException e) {
+ return super.toString();
+ }
+ }
+
+ public void setTagBits(long tagBits) {
+ TAG_BITS.put(getNd(), this.address, tagBits);
+ }
+
+ public long getTagBits() {
+ return TAG_BITS.get(getNd(), this.address);
+ }
+
+ public void setFieldDescriptorFromClass(char[] fieldDescriptorFromClass) {
+ FIELD_DESCRIPTOR_FROM_CLASS.put(getNd(), this.address, fieldDescriptorFromClass);
+ }
+
+ /**
+ * Returns the field descriptor for this type, based on the binary type information stored in the
+ * .class file itself. Note that this may differ from the field descriptor of this type's typeId in
+ * the event that the .class file has been moved.
+ */
+ public IString getFieldDescriptor() {
+ IString descriptorFromClass = FIELD_DESCRIPTOR_FROM_CLASS.get(getNd(), this.address);
+ if (descriptorFromClass.length() == 0) {
+ return getTypeId().getFieldDescriptor();
+ }
+ return descriptorFromClass;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java
new file mode 100644
index 000000000..e9bfa4a4d
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotation.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldPointer;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotation extends NdAnnotation {
+ public static final FieldByte TARGET_TYPE;
+ public static final FieldByte TARGET_ARG0;
+ public static final FieldByte TARGET_ARG1;
+ public static final FieldByte PATH_LENGTH;
+ public static final FieldPointer PATH;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeAnnotation> type;
+
+ static {
+ type = StructDef.create(NdTypeAnnotation.class, NdAnnotation.type);
+ TARGET_TYPE = type.addByte();
+ TARGET_ARG0 = type.addByte();
+ TARGET_ARG1 = type.addByte();
+ PATH_LENGTH = type.addByte();
+ PATH = type.addPointer();
+ type.done();
+ }
+
+ private static final byte[] NO_TYPE_PATH = new byte[0];
+
+ public NdTypeAnnotation(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeAnnotation(Nd nd) {
+ super(nd);
+ }
+
+ public void setPath(byte[] path) {
+ freePath();
+ Nd nd = getNd();
+ PATH_LENGTH.put(nd, this.address, (byte)path.length);
+ if (path.length > 0) {
+ long pathArray = nd.getDB().malloc(path.length, Database.POOL_MISC);
+ PATH.put(nd, this.address, pathArray);
+ nd.getDB().putBytes(pathArray, path, path.length);
+ }
+ }
+
+ public void setTargetInfo(int arg) {
+ TARGET_ARG0.put(getNd(), this.address, (byte)((arg >> 8) & 0xff));
+ TARGET_ARG1.put(getNd(), this.address, (byte)(arg & 0xff));
+ }
+
+ public byte getTargetInfoArg0() {
+ return TARGET_ARG0.get(getNd(), this.address);
+ }
+
+ public byte getTargetInfoArg1() {
+ return TARGET_ARG1.get(getNd(), this.address);
+ }
+
+ public int getTarget() {
+ int arg0 = TARGET_ARG0.get(getNd(), this.address) & 0xff;
+ int arg1 = TARGET_ARG1.get(getNd(), this.address) & 0xff;
+ int result = (arg0 << 8) | arg1;
+ return result;
+ }
+
+ public void setTargetInfo(byte arg0, byte arg1) {
+ TARGET_ARG0.put(getNd(), this.address, arg0);
+ TARGET_ARG1.put(getNd(), this.address, arg1);
+ }
+
+ /**
+ * @param targetType one of the constants from {@link AnnotationTargetTypeConstants}
+ */
+ public void setTargetType(int targetType) {
+ TARGET_TYPE.put(getNd(), this.address, (byte)targetType);
+ }
+
+ /**
+ * @return one of the constants from {@link AnnotationTargetTypeConstants}
+ */
+ public int getTargetType() {
+ return TARGET_TYPE.get(getNd(), this.address);
+ }
+
+ public byte[] getTypePath() {
+ long pathPointer = PATH.get(getNd(), this.address);
+ if (pathPointer == 0) {
+ return NO_TYPE_PATH;
+ }
+ int pathLength = PATH_LENGTH.get(getNd(), this.address);
+ byte[] result = new byte[pathLength];
+ getNd().getDB().getBytes(pathPointer, result);
+ return result;
+ }
+
+ @Override
+ public void destruct() {
+ freePath();
+ super.destruct();
+ }
+
+ private void freePath() {
+ Nd nd = getNd();
+ long pathPointer = PATH.get(nd, this.address);
+ nd.getDB().free(pathPointer, Database.POOL_MISC);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java
new file mode 100644
index 000000000..894a1d549
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInMethod.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotationInMethod extends NdTypeAnnotation {
+ public static final FieldManyToOne<NdMethod> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeAnnotationInMethod> type;
+
+ static {
+ type = StructDef.create(NdTypeAnnotationInMethod.class, NdTypeAnnotation.type);
+ OWNER = FieldManyToOne.createOwner(type, NdMethod.TYPE_ANNOTATIONS);
+ type.done();
+ }
+
+ public NdTypeAnnotationInMethod(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeAnnotationInMethod(Nd nd, NdMethod variable) {
+ super(nd);
+
+ OWNER.put(getNd(), this.address, variable);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java
new file mode 100644
index 000000000..7aff10908
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInType.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotationInType extends NdTypeAnnotation {
+ public static final FieldManyToOne<NdType> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeAnnotationInType> type;
+
+ static {
+ type = StructDef.create(NdTypeAnnotationInType.class, NdTypeAnnotation.type);
+ OWNER = FieldManyToOne.createOwner(type, NdType.TYPE_ANNOTATIONS);
+ type.done();
+ }
+
+ public NdTypeAnnotationInType(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeAnnotationInType(Nd nd, NdType type) {
+ super(nd);
+
+ OWNER.put(getNd(), this.address, type);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java
new file mode 100644
index 000000000..eb591fe06
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeAnnotationInVariable.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdTypeAnnotationInVariable extends NdTypeAnnotation {
+ public static final FieldManyToOne<NdVariable> OWNER;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeAnnotationInVariable> type;
+
+ static {
+ type = StructDef.create(NdTypeAnnotationInVariable.class, NdTypeAnnotation.type);
+ OWNER = FieldManyToOne.createOwner(type, NdVariable.TYPE_ANNOTATIONS);
+ type.done();
+ }
+
+ public NdTypeAnnotationInVariable(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeAnnotationInVariable(Nd nd, NdVariable variable) {
+ super(nd);
+
+ OWNER.put(getNd(), this.address, variable);
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeArgument.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeArgument.java
new file mode 100644
index 000000000..37af73ac3
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeArgument.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdTypeArgument extends NdNode {
+ public static final FieldManyToOne<NdComplexTypeSignature> PARENT;
+ public static final FieldManyToOne<NdTypeSignature> TYPE_SIGNATURE;
+ public static final FieldByte WILDCARD;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeArgument> type;
+
+ static {
+ type = StructDef.create(NdTypeArgument.class, NdNode.type);
+ PARENT = FieldManyToOne.createOwner(type, NdComplexTypeSignature.TYPE_ARGUMENTS);
+ TYPE_SIGNATURE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_TYPE_ARGUMENT);
+ WILDCARD = type.addByte();
+ type.done();
+ }
+
+ public static final int WILDCARD_NONE = 0;
+ public static final int WILDCARD_EXTENDS = 1;
+ public static final int WILDCARD_SUPER = 2;
+ public static final int WILDCARD_QUESTION = 3;
+
+ public NdTypeArgument(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeArgument(Nd nd, NdComplexTypeSignature typeSignature) {
+ super(nd);
+
+ PARENT.put(nd, this.address, typeSignature);
+ }
+
+ /**
+ * Sets the wildcard to use, one of the WILDCARD_* constants.
+ *
+ * @param wildcard
+ */
+ public void setWildcard(int wildcard) {
+ WILDCARD.put(getNd(), this.address, (byte) wildcard);
+ }
+
+ public void setType(NdTypeSignature typeSignature) {
+ TYPE_SIGNATURE.put(getNd(), this.address, typeSignature);
+ }
+
+ public int getWildcard() {
+ return WILDCARD.get(getNd(), this.address);
+ }
+
+ public NdComplexTypeSignature getParent() {
+ return PARENT.get(getNd(), this.address);
+ }
+
+ public NdTypeSignature getType() {
+ return TYPE_SIGNATURE.get(getNd(), this.address);
+ }
+
+ public void getSignature(CharArrayBuffer result) {
+ switch (getWildcard()) {
+ case NdTypeArgument.WILDCARD_EXTENDS: result.append('-'); break;
+ case NdTypeArgument.WILDCARD_QUESTION: result.append('*'); return;
+ case NdTypeArgument.WILDCARD_SUPER: result.append('+'); break;
+ }
+
+ NdTypeSignature theType = getType();
+ if (theType != null) {
+ theType.getSignature(result);
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java
new file mode 100644
index 000000000..c6c827eee
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeBound.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Represents the bound on a generic parameter (a ClassBound or InterfaceBound in
+ * the sense of the Java VM spec Java SE 8 Edition, section 4.7.9.1).
+ */
+public class NdTypeBound extends NdNode {
+ public static final FieldManyToOne<NdTypeParameter> PARENT;
+ public static final FieldManyToOne<NdTypeSignature> TYPE;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeBound> type;
+
+ static {
+ type = StructDef.create(NdTypeBound.class, NdNode.type);
+ PARENT = FieldManyToOne.createOwner(type, NdTypeParameter.BOUNDS);
+ TYPE = FieldManyToOne.create(type, NdTypeSignature.USED_AS_TYPE_BOUND);
+
+ type.done();
+ }
+
+ public NdTypeBound(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeBound(NdTypeParameter parent, NdTypeSignature signature) {
+ super(parent.getNd());
+
+ PARENT.put(getNd(), this.address, parent);
+ TYPE.put(getNd(), this.address, signature);
+ }
+
+ public NdTypeParameter getParent() {
+ return PARENT.get(getNd(), this.address);
+ }
+
+ public NdTypeSignature getType() {
+ return TYPE.get(getNd(), this.address);
+ }
+
+ public void getSignature(CharArrayBuffer result) {
+ result.append(':');
+ getType().getSignature(result);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeId.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeId.java
new file mode 100644
index 000000000..a132f97f9
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeId.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchKey;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+public class NdTypeId extends NdTypeSignature {
+ public static final FieldSearchKey<JavaIndex> FIELD_DESCRIPTOR;
+ public static final FieldSearchKey<JavaIndex> SIMPLE_NAME;
+ public static final FieldOneToMany<NdType> TYPES;
+ public static final FieldOneToMany<NdComplexTypeSignature> USED_AS_COMPLEX_TYPE;
+ public static final FieldOneToMany<NdType> DECLARED_TYPES;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeId> type;
+
+ private String fName;
+
+ static {
+ type = StructDef.create(NdTypeId.class, NdTypeSignature.type);
+ FIELD_DESCRIPTOR = FieldSearchKey.create(type, JavaIndex.TYPES);
+ SIMPLE_NAME = FieldSearchKey.create(type, JavaIndex.SIMPLE_INDEX);
+ TYPES = FieldOneToMany.create(type, NdType.TYPENAME, 2);
+ USED_AS_COMPLEX_TYPE = FieldOneToMany.create(type, NdComplexTypeSignature.RAW_TYPE);
+ DECLARED_TYPES = FieldOneToMany.create(type, NdType.DECLARING_TYPE);
+ type.useStandardRefCounting().done();
+ }
+
+ public NdTypeId(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeId(Nd nd, char[] fieldDescriptor) {
+ super(nd);
+
+ char[] simpleName = JavaNames.fieldDescriptorToJavaName(fieldDescriptor, false);
+ FIELD_DESCRIPTOR.put(nd, this.address, fieldDescriptor);
+ SIMPLE_NAME.put(nd, this.address, simpleName);
+ }
+
+ @Override
+ public List<NdType> getSubTypes() {
+ List<NdType> result = new ArrayList<>();
+ result.addAll(super.getSubTypes());
+ for (NdComplexTypeSignature next : getComplexTypes()) {
+ result.addAll(next.getSubTypes());
+ }
+ return result;
+ }
+
+ public List<NdComplexTypeSignature> getComplexTypes() {
+ return USED_AS_COMPLEX_TYPE.asList(getNd(), this.address);
+ }
+
+ public NdType findTypeByResourceAddress(long resourceAddress) {
+ int size = TYPES.size(getNd(), this.address);
+ for (int idx = 0; idx < size; idx++) {
+ NdType next = TYPES.get(getNd(), this.address, idx);
+
+ if (next.getResourceAddress() == resourceAddress) {
+ return next;
+ }
+ }
+ return null;
+ }
+
+ public List<NdType> getTypes() {
+ return TYPES.asList(getNd(), this.address);
+ }
+
+ /**
+ * Returns the field descriptor.
+ */
+ public IString getFieldDescriptor() {
+ return FIELD_DESCRIPTOR.get(getNd(), this.address);
+ }
+
+ public char[] getFieldDescriptorWithoutTrailingSemicolon() {
+ char[] fieldDescriptor = getFieldDescriptor().getChars();
+
+ int end = fieldDescriptor.length;
+ if (fieldDescriptor.length > 0 && fieldDescriptor[end - 1] == ';') {
+ end--;
+ }
+
+ return CharArrayUtils.subarray(fieldDescriptor, 0, end);
+ }
+
+ public char[] getBinaryName() {
+ return JavaNames.fieldDescriptorToBinaryName(getFieldDescriptor().getChars());
+ }
+
+ public IString getSimpleName() {
+ return SIMPLE_NAME.get(getNd(), this.address);
+ }
+
+ public char[] getSimpleNameCharArray() {
+ if (this.fName == null) {
+ this.fName= getSimpleName().getString();
+ }
+ return this.fName.toCharArray();
+ }
+
+ public boolean hasFieldDescriptor(String name) {
+ return this.getFieldDescriptor().compare(name, true) == 0;
+ }
+
+ public boolean hasSimpleName(String name) {
+ if (this.fName != null)
+ return this.fName.equals(name);
+
+ return getSimpleName().equals(name);
+ }
+
+ public void setSimpleName(String name) {
+ if (Objects.equals(name, this.fName)) {
+ return;
+ }
+ this.fName = name;
+ SIMPLE_NAME.put(getNd(), this.address, name);
+ }
+
+ public List<NdType> getDeclaredTypes() {
+ return DECLARED_TYPES.asList(getNd(), this.address);
+ }
+
+ @Override
+ public NdTypeId getRawType() {
+ return this;
+ }
+
+ @Override
+ public void getSignature(CharArrayBuffer result, boolean includeTrailingSemicolon) {
+ if (includeTrailingSemicolon) {
+ result.append(getFieldDescriptor().getChars());
+ } else {
+ result.append(getFieldDescriptorWithoutTrailingSemicolon());
+ }
+ }
+
+ @Override
+ public boolean isTypeVariable() {
+ return false;
+ }
+
+ @Override
+ public List<NdTypeSignature> getDeclaringTypeChain() {
+ return Collections.singletonList((NdTypeSignature)this);
+ }
+
+ @Override
+ public NdTypeSignature getArrayDimensionType() {
+ return null;
+ }
+
+ @Override
+ public List<NdTypeArgument> getTypeArguments() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean isArrayType() {
+ return false;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeInterface.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeInterface.java
new file mode 100644
index 000000000..ca8ae778f
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeInterface.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Represents one interface implemented by a specific type. This is an intermediate object between a {@link NdType} and
+ * the {@link NdTypeId}s corresponding to its interfaces, which is necessary in order to implement the many-to-many
+ * relationship between them.
+ */
+public class NdTypeInterface extends NdNode {
+ public static final FieldManyToOne<NdType> APPLIES_TO;
+ public static final FieldManyToOne<NdTypeSignature> IMPLEMENTS;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdTypeInterface> type;
+
+ static {
+ type = StructDef.create(NdTypeInterface.class, NdNode.type);
+ APPLIES_TO = FieldManyToOne.createOwner(type, NdType.INTERFACES);
+ IMPLEMENTS = FieldManyToOne.create(type, NdTypeSignature.IMPLEMENTATIONS);
+ type.done();
+ }
+
+ public NdTypeInterface(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeInterface(Nd nd, NdType targetType, NdTypeSignature makeTypeId) {
+ super(nd);
+
+ APPLIES_TO.put(nd, this.address, targetType);
+ IMPLEMENTS.put(nd, this.address, makeTypeId);
+ }
+
+ public NdType getImplementation() {
+ return APPLIES_TO.get(getNd(), this.address);
+ }
+
+ public NdTypeSignature getInterface() {
+ return IMPLEMENTS.get(getNd(), this.address);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java
new file mode 100644
index 000000000..fda4676b1
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeParameter.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Represents a TypeParameter, as described in Section 4.7.9.1 of the java VM specification, Java SE 8 edititon.
+ */
+public class NdTypeParameter extends NdNode {
+ public static final FieldManyToOne<NdBinding> PARENT;
+ public static final FieldString IDENTIFIER;
+ public static final FieldOneToMany<NdTypeBound> BOUNDS;
+ public static final FieldByte TYPE_PARAMETER_FLAGS;
+
+ public static final byte FLG_FIRST_BOUND_IS_A_CLASS = 0x01;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdTypeParameter> type;
+
+ static {
+ type = StructDef.create(NdTypeParameter.class, NdNode.type);
+ PARENT = FieldManyToOne.createOwner(type, NdBinding.TYPE_PARAMETERS);
+ IDENTIFIER = type.addString();
+ BOUNDS = FieldOneToMany.create(type, NdTypeBound.PARENT);
+ TYPE_PARAMETER_FLAGS = type.addByte();
+
+ type.done();
+ }
+
+ public NdTypeParameter(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeParameter(NdBinding parent, char[] identifier) {
+ super(parent.getNd());
+
+ PARENT.put(getNd(), this.address, parent);
+ IDENTIFIER.put(getNd(), this.address, identifier);
+ }
+
+ public char[] getIdentifier() {
+ return IDENTIFIER.get(getNd(), this.address).getChars();
+ }
+
+ public void setFirstBoundIsClass(boolean isClass) {
+ setFlag(FLG_FIRST_BOUND_IS_A_CLASS, isClass);
+ }
+
+ public boolean isFirstBoundAClass() {
+ return (TYPE_PARAMETER_FLAGS.get(getNd(), this.address) & FLG_FIRST_BOUND_IS_A_CLASS) != 0;
+ }
+
+ private void setFlag(byte flag, boolean value) {
+ byte oldValue = TYPE_PARAMETER_FLAGS.get(getNd(), this.address);
+ byte newValue;
+ if (value) {
+ newValue = (byte) (oldValue | flag);
+ } else {
+ newValue = (byte) (oldValue & ~flag);
+ }
+ TYPE_PARAMETER_FLAGS.put(getNd(), this.address, newValue);
+ }
+
+ public List<NdTypeBound> getBounds() {
+ return BOUNDS.asList(getNd(), this.address);
+ }
+
+ public void getSignature(CharArrayBuffer result) {
+ result.append(getIdentifier());
+
+ List<NdTypeBound> bounds = getBounds();
+
+ // If none of the bounds are classes and there is at least one bound, then insert a double-colon
+ // in the type signature.
+ if (!bounds.isEmpty() && !isFirstBoundAClass()) {
+ result.append(':');
+ }
+
+ for (NdTypeBound next : bounds) {
+ next.getSignature(result);
+ }
+ }
+
+ public static void getSignature(CharArrayBuffer buffer, List<NdTypeParameter> params) {
+ if (!params.isEmpty()) {
+ buffer.append('<');
+ for (NdTypeParameter next : params) {
+ next.getSignature(buffer);
+ }
+ buffer.append('>');
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeSignature.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeSignature.java
new file mode 100644
index 000000000..207963684
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdTypeSignature.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Corresponds roughly to a JavaTypeSignature, as described in section 4.7.9.1 of the Java VM spec version 4, with the
+ * addition of annotations and backpointers to locations where the type is used.
+ * <p>
+ * Holds back-pointers to all the entities that refer to the name, along with pointers to all classes that have this
+ * name. Note that this isn't the class declaration itself. The same index can hold multiple jar files, some of which
+ * may contain classes with the same name. All classes that use this fully-qualified name point to the same
+ * {@link NdTypeSignature}.
+ * <p>
+ * Other entities should refer to a type via its TypeId if there is any possiblity that the type may change based on the
+ * classpath. It should refer to the type directly if there is no possibility for a type lookup. For example, nested
+ * classes refer to their enclosing class directly since they live in the same file and there is no possibility for the
+ * enclosing class to change based on the classpath. Classes refer to their base class via its TypeId since the parent
+ * class might live in a different jar and need to be resolved on the classpath.
+ */
+public abstract class NdTypeSignature extends NdNode {
+ public static final FieldOneToMany<NdType> SUBCLASSES;
+ public static final FieldOneToMany<NdAnnotation> ANNOTATIONS_OF_THIS_TYPE;
+ public static final FieldOneToMany<NdTypeInterface> IMPLEMENTATIONS;
+ public static final FieldOneToMany<NdVariable> VARIABLES_OF_TYPE;
+ public static final FieldOneToMany<NdConstantClass> USED_AS_CONSTANT;
+ public static final FieldOneToMany<NdConstantEnum> USED_AS_ENUM_CONSTANT;
+ public static final FieldOneToMany<NdTypeArgument> USED_AS_TYPE_ARGUMENT;
+ public static final FieldOneToMany<NdTypeBound> USED_AS_TYPE_BOUND;
+ public static final FieldOneToMany<NdMethodParameter> USED_AS_METHOD_ARGUMENT;
+ public static final FieldOneToMany<NdMethodException> USED_AS_EXCEPTION;
+ public static final FieldOneToMany<NdMethod> USED_AS_RETURN_TYPE;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdTypeSignature> type;
+
+ static {
+ type = StructDef.createAbstract(NdTypeSignature.class, NdNode.type);
+ SUBCLASSES = FieldOneToMany.create(type, NdType.SUPERCLASS);
+ ANNOTATIONS_OF_THIS_TYPE = FieldOneToMany.create(type, NdAnnotation.ANNOTATION_TYPE);
+ IMPLEMENTATIONS = FieldOneToMany.create(type, NdTypeInterface.IMPLEMENTS);
+ VARIABLES_OF_TYPE = FieldOneToMany.create(type, NdVariable.TYPE);
+ USED_AS_CONSTANT = FieldOneToMany.create(type, NdConstantClass.VALUE);
+ USED_AS_ENUM_CONSTANT = FieldOneToMany.create(type, NdConstantEnum.ENUM_TYPE);
+ USED_AS_TYPE_ARGUMENT = FieldOneToMany.create(type, NdTypeArgument.TYPE_SIGNATURE);
+ USED_AS_TYPE_BOUND = FieldOneToMany.create(type, NdTypeBound.TYPE);
+ USED_AS_METHOD_ARGUMENT = FieldOneToMany.create(type, NdMethodParameter.ARGUMENT_TYPE);
+ USED_AS_EXCEPTION = FieldOneToMany.create(type, NdMethodException.EXCEPTION_TYPE);
+ USED_AS_RETURN_TYPE = FieldOneToMany.create(type, NdMethod.RETURN_TYPE);
+ type.useStandardRefCounting().done();
+ }
+
+ public NdTypeSignature(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdTypeSignature(Nd nd) {
+ super(nd);
+ }
+
+ public List<NdType> getSubclasses() {
+ return SUBCLASSES.asList(getNd(), this.address);
+ }
+
+ public List<NdTypeInterface> getImplementations() {
+ return IMPLEMENTATIONS.asList(getNd(), this.address);
+ }
+
+ /**
+ * Returns all subclasses (for classes) and implementations (for interfaces) of this type
+ */
+ public List<NdType> getSubTypes() {
+ List<NdType> result = new ArrayList<>();
+ result.addAll(getSubclasses());
+
+ for (NdTypeInterface next : getImplementations()) {
+ result.add(next.getImplementation());
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the raw version of this type, if one exists. That is, the version of this type
+ * without any generic arguments or annotations, which the java runtime sees. Returns null
+ * of this signature doesn't have a raw type, for example if it is a type variable.
+ */
+ public abstract NdTypeId getRawType();
+
+ public final void getSignature(CharArrayBuffer result) {
+ getSignature(result, true);
+ }
+
+ public abstract void getSignature(CharArrayBuffer result, boolean includeTrailingSemicolon);
+
+ /**
+ * Returns true iff this is an array type signature (ie: that getArrayDimensionType() will return a non-null
+ * answer). Note that this only returns true for the type signature that holds the reference to the array dimension
+ * type. The raw type for that signature will return false, even though it has a field descriptor starting with '['.
+ * <p>
+ * In other words:
+ *
+ * <pre>
+ * NdVariable someVariable = getSomeVariableWithAnArrayType()
+ * System.out.println(someVariable.getType().isArrayType()); // true
+ * System.out.println(someVariable.getType().getRawType().isArrayType()); // false
+ * </pre>
+ */
+ public abstract boolean isArrayType();
+
+ public abstract boolean isTypeVariable();
+
+ /**
+ * Returns the chain of declaring generic types. The first element in the chain is a top-level type and the
+ * receiver is the last element in the chain.
+ */
+ public abstract List<NdTypeSignature> getDeclaringTypeChain();
+
+ /**
+ * If the receiver is an array type, it returns the signature of the array's next dimension. Returns null if
+ * this is not an array type.
+ */
+ public abstract NdTypeSignature getArrayDimensionType();
+
+ /**
+ * Returns the type arguments for this type signature, if any. Returns the empty list if none.
+ */
+ public abstract List<NdTypeArgument> getTypeArguments();
+
+ public String toString() {
+ try {
+ CharArrayBuffer result = new CharArrayBuffer();
+ getSignature(result);
+ return result.toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
new file mode 100644
index 000000000..e85f805ac
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdVariable.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldByte;
+import org.eclipse.jdt.internal.core.nd.field.FieldInt;
+import org.eclipse.jdt.internal.core.nd.field.FieldLong;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToMany;
+import org.eclipse.jdt.internal.core.nd.field.FieldOneToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+public class NdVariable extends NdBinding {
+ public static final FieldManyToOne<NdTypeSignature> TYPE;
+ public static final FieldInt VARIABLE_ID;
+ public static final FieldManyToOne<NdMethod> DECLARING_METHOD;
+ public static final FieldManyToOne<NdBinding> PARENT;
+ public static final FieldString NAME;
+ public static final FieldOneToOne<NdConstant> CONSTANT;
+ public static final FieldLong TAG_BITS;
+ public static final FieldByte VARIABLE_FLAGS;
+ public static final FieldOneToMany<NdAnnotationInVariable> ANNOTATIONS;
+ public static final FieldOneToMany<NdTypeAnnotationInVariable> TYPE_ANNOTATIONS;
+
+ @SuppressWarnings("hiding")
+ public static StructDef<NdVariable> type;
+
+ public static final byte FLG_GENERIC_SIGNATURE_PRESENT = 0x01;
+
+ static {
+ type = StructDef.create(NdVariable.class, NdBinding.type);
+ TYPE = FieldManyToOne.create(type, NdTypeSignature.VARIABLES_OF_TYPE);
+ VARIABLE_ID = type.addInt();
+ DECLARING_METHOD = FieldManyToOne.create(type, NdMethod.DECLARED_VARIABLES);
+ PARENT = FieldManyToOne.create(type, NdBinding.VARIABLES);
+ NAME = type.addString();
+ CONSTANT = FieldOneToOne.create(type, NdConstant.class, NdConstant.PARENT_VARIABLE);
+ TAG_BITS = type.addLong();
+ VARIABLE_FLAGS = type.addByte();
+ ANNOTATIONS = FieldOneToMany.create(type, NdAnnotationInVariable.OWNER);
+ TYPE_ANNOTATIONS = FieldOneToMany.create(type, NdTypeAnnotationInVariable.OWNER);
+ type.done();
+ }
+
+ public NdVariable(Nd nd, long bindingRecord) {
+ super(nd, bindingRecord);
+ }
+
+ public NdVariable(NdBinding parent) {
+ super(parent.getNd(), parent.getFile());
+
+ PARENT.put(getNd(), this.address, parent);
+ }
+
+ public boolean hasVariableFlag(int toTest) {
+ return (VARIABLE_FLAGS.get(getNd(), this.address) & toTest) != 0;
+ }
+
+ public void setVariableFlag(byte toSet) {
+ int newFlags = VARIABLE_FLAGS.get(getNd(), this.address) | toSet;
+ VARIABLE_FLAGS.put(getNd(), this.address, (byte)newFlags);
+ }
+
+ public void setName(char[] name) {
+ NAME.put(getNd(), this.address, name);
+ }
+
+ public IString getName() {
+ return NAME.get(getNd(), this.address);
+ }
+
+ public void setType(NdTypeSignature typeId) {
+ TYPE.put(getNd(), this.address, typeId);
+ }
+
+ public void setConstant(NdConstant constant) {
+ CONSTANT.put(getNd(), this.address, constant);
+ }
+
+ public NdConstant getConstant() {
+ return CONSTANT.get(getNd(), this.address);
+ }
+
+ public NdTypeSignature getType() {
+ return TYPE.get(getNd(), this.address);
+ }
+
+ public long getTagBits() {
+ return TAG_BITS.get(getNd(), this.address);
+ }
+
+ public void setTagBits(long tagBits) {
+ TAG_BITS.put(getNd(), this.address, tagBits);
+ }
+
+ public List<NdTypeAnnotationInVariable> getTypeAnnotations() {
+ return TYPE_ANNOTATIONS.asList(getNd(), this.address);
+ }
+
+ public List<NdAnnotationInVariable> getAnnotations() {
+ return ANNOTATIONS.asList(getNd(), this.address);
+ }
+
+ public String toString() {
+ try {
+ StringBuilder result = new StringBuilder();
+ NdTypeSignature localType = getType();
+ if (localType != null) {
+ result.append(localType.toString());
+ result.append(" "); //$NON-NLS-1$
+ }
+ IString name = getName();
+ if (name != null) {
+ result.append(name.toString());
+ }
+ NdConstant constant = getConstant();
+ if (constant != null) {
+ result.append(" = "); //$NON-NLS-1$
+ result.append(constant.toString());
+ }
+ return result.toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdWorkspaceLocation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdWorkspaceLocation.java
new file mode 100644
index 000000000..8e52b8bfd
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/NdWorkspaceLocation.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.NdNode;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.field.FieldManyToOne;
+import org.eclipse.jdt.internal.core.nd.field.FieldString;
+import org.eclipse.jdt.internal.core.nd.field.StructDef;
+
+/**
+ * Holds a location in the Eclipse workspace where a given resource was found. Note that a given
+ * resource might be mapped to multiple locations in the workspace.
+ */
+public class NdWorkspaceLocation extends NdNode {
+ public static final FieldManyToOne<NdResourceFile> RESOURCE;
+ public static final FieldString PATH;
+
+ @SuppressWarnings("hiding")
+ public static final StructDef<NdWorkspaceLocation> type;
+
+ static {
+ type = StructDef.create(NdWorkspaceLocation.class, NdNode.type);
+ RESOURCE = FieldManyToOne.createOwner(type, NdResourceFile.WORKSPACE_MAPPINGS);
+ PATH = type.addString();
+ type.done();
+ }
+
+ public NdWorkspaceLocation(Nd nd, long address) {
+ super(nd, address);
+ }
+
+ public NdWorkspaceLocation(Nd nd, NdResourceFile resource, char[] path) {
+ super(nd);
+
+ RESOURCE.put(getNd(), this.address, resource);
+ PATH.put(getNd(), this.address, path);
+ }
+
+ public IString getPath() {
+ return PATH.get(getNd(), this.address);
+ }
+
+ public NdResourceFile getResourceFile() {
+ return RESOURCE.get(getNd(), this.address);
+ }
+
+ public String toString() {
+ try {
+ return getPath().toString();
+ } catch (RuntimeException e) {
+ // This is called most often from the debugger, so we want to return something meaningful even
+ // if the code is buggy, the database is corrupt, or we don't have a read lock.
+ return super.toString();
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/Package.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/Package.java
new file mode 100644
index 000000000..9903317bc
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/Package.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+
+/* package */ class Package {
+ public static String PLUGIN_ID = JavaCore.PLUGIN_ID;
+
+ public static void log(Throwable e) {
+ String msg= e.getMessage();
+ if (msg == null) {
+ log("Error", e); //$NON-NLS-1$
+ } else {
+ log("Error: " + msg, e); //$NON-NLS-1$
+ }
+ }
+
+ public static void log(String message, Throwable e) {
+ log(createStatus(message, e));
+ }
+
+ public static IStatus createStatus(String msg, Throwable e) {
+ return new Status(IStatus.ERROR, PLUGIN_ID, msg, e);
+ }
+
+ public static IStatus createStatus(String msg) {
+ return new Status(IStatus.ERROR, PLUGIN_ID, msg);
+ }
+
+ public static void log(IStatus status) {
+ JavaCore.getPlugin().getLog().log(status);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TagTreeReader.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TagTreeReader.java
new file mode 100644
index 000000000..9ad5a6095
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TagTreeReader.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.Database;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+
+public abstract class TagTreeReader {
+ public static final int[] UNUSED_RESULT = new int[1];
+
+ public static abstract class TagHandler<T> {
+ abstract public T read(Nd nd, long address, TagTreeReader reader, int[] bytesRead);
+ abstract public void write(Nd nd, long address, TagTreeReader reader, T toWrite, int[] bytesWritten);
+ abstract public int getSize(Nd nd, T object, TagTreeReader reader);
+ public void destruct(Nd nd, long address, TagTreeReader reader) {
+ // Nothing to do by default
+ }
+ }
+
+ public static abstract class FixedSizeTagHandler<T> extends TagHandler<T> {
+ protected abstract T read(Nd nd, long address);
+ protected abstract void write(Nd nd, long address, T value);
+ protected abstract int getSize();
+ protected void destruct(Nd nd, long address) {
+ // Nothing to do by default
+ }
+
+ public final T read(Nd nd, long address, TagTreeReader reader, int[] bytesRead) {
+ bytesRead[0] = getSize();
+ return read(nd, address);
+ }
+
+ @Override
+ public final void write(Nd nd, long address, TagTreeReader reader, T value, int[] bytesWritten) {
+ bytesWritten[0] = getSize();
+ write(nd, address, value);
+ }
+
+ @Override
+ public final int getSize(Nd nd, T object, TagTreeReader reader) {
+ return getSize();
+ }
+
+ @Override
+ public final void destruct(Nd nd, long address, TagTreeReader reader) {
+ destruct(nd, address);
+ }
+ }
+
+ private TagHandler<?> readers[] = new TagHandler[256];
+ private Map<TagHandler<?>, Integer> values = new HashMap<>();
+
+ public final void add(byte key, TagHandler<?> reader) {
+ this.readers[key] = reader;
+ this.values.put(reader, (int) key);
+ }
+
+ public final Object read(Nd nd, long address) {
+ return read(nd, address, UNUSED_RESULT);
+ }
+
+ public final Object read(Nd nd, long address, int[] bytesRead) {
+ long readAddress = address;
+ Database db = nd.getDB();
+ byte nextByte = db.getByte(address);
+ readAddress += Database.BYTE_SIZE;
+ TagHandler<?> reader = this.readers[nextByte];
+ if (reader == null) {
+ throw new IndexException("Found unknown tag with value " + nextByte + " at address " + address); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ return reader.read(nd, readAddress, this, bytesRead);
+ }
+
+ protected abstract byte getKeyFor(Object toWrite);
+
+ public final void write(Nd nd, long address, Object toWrite) {
+ write(nd, address, toWrite, UNUSED_RESULT);
+ }
+
+ @SuppressWarnings("unchecked")
+ public final void write(Nd nd, long address, Object toWrite, int[] bytesWritten) {
+ byte key = getKeyFor(toWrite);
+
+ @SuppressWarnings("rawtypes")
+ TagHandler handler = this.readers[key];
+
+ if (handler == null) {
+ throw new IndexException("Invalid key " + key + " returned from getKeyFor(...)"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ handler.write(nd, address, this, toWrite, bytesWritten);
+ }
+
+ public final void destruct(Nd nd, long address) {
+ Database db = nd.getDB();
+ long readAddress = address;
+ byte nextByte = db.getByte(readAddress);
+ readAddress += Database.BYTE_SIZE;
+
+ TagHandler<?> handler = this.readers[nextByte];
+ if (handler == null) {
+ throw new IndexException("Found unknown tag with value " + nextByte + " at address " + address); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ handler.destruct(nd, readAddress, this);
+ }
+
+ @SuppressWarnings("unchecked")
+ public final int getSize(Nd nd, Object toMeasure) {
+ byte key = getKeyFor(toMeasure);
+
+ @SuppressWarnings("rawtypes")
+ TagHandler handler = this.readers[key];
+ if (handler == null) {
+ throw new IndexException("Attempted to get size of object " + toMeasure.toString() + " with unknown key " //$NON-NLS-1$//$NON-NLS-2$
+ + key);
+ }
+
+ return handler.getSize(nd, toMeasure, this);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TypeRef.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TypeRef.java
new file mode 100644
index 000000000..4e65b4b05
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/TypeRef.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.eclipse.jdt.internal.core.nd.DatabaseRef;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+
+/**
+ * Holds a reference to an NdType that can be retained while releasing and reacquiring a read lock.
+ */
+public final class TypeRef implements Supplier<NdType> {
+ final DatabaseRef<NdType> ref;
+ final char[] fileName;
+ final char[] fieldDescriptor;
+ final TypeSupplier typeSupplier = new TypeSupplier();
+ private final class TypeSupplier implements Supplier<NdType> {
+ public TypeSupplier() {
+ }
+
+ @Override
+ public NdType get() {
+ NdTypeId typeId = JavaIndex.getIndex(TypeRef.this.ref.getNd()).findType(TypeRef.this.fieldDescriptor);
+
+ if (typeId == null) {
+ return null;
+ }
+
+ List<NdType> implementations = typeId.getTypes();
+ for (NdType next : implementations) {
+ NdResourceFile nextResourceFile = next.getResourceFile();
+ if (nextResourceFile.getLocation().compare(TypeRef.this.fileName, false) == 0) {
+ if (nextResourceFile.isDoneIndexing()) {
+ return next;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ private TypeRef(NdType type) {
+ super();
+ this.fieldDescriptor = type.getTypeId().getRawType().getFieldDescriptor().getChars();
+ this.fileName = type.getResourceFile().getLocation().getChars();
+ this.ref = new DatabaseRef<NdType>(type.getNd(), this.typeSupplier, type);
+ }
+
+ private TypeRef(Nd nd, char[] resourcePath, char[] fieldDescriptor) {
+ super();
+ this.fieldDescriptor = fieldDescriptor;
+ this.fileName = resourcePath;
+ this.ref = new DatabaseRef<NdType>(nd, this.typeSupplier);
+ }
+
+ public char[] getFieldDescriptor() {
+ return this.fieldDescriptor;
+ }
+
+ public char[] getFileName() {
+ return this.fileName;
+ }
+
+ /**
+ * Creates a {@link DatabaseRef} to the given {@link NdType}.
+ */
+ public static TypeRef create(NdType type) {
+ return new TypeRef(type);
+ }
+
+ /**
+ * Creates a {@link DatabaseRef} to the {@link NdType} with the given resource path and field descriptor.
+ */
+ public static TypeRef create(Nd nd, char[] resourcePath, char[] fieldDescriptor) {
+ return new TypeRef(nd, resourcePath, fieldDescriptor);
+ }
+
+ public IReader lock() {
+ return this.ref.lock();
+ }
+
+ @Override
+ public NdType get() {
+ return this.ref.get();
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeDescriptor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeDescriptor.java
new file mode 100644
index 000000000..393537b21
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeDescriptor.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IDependent;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+
+/**
+ * Holds a lightweight identifier for an IBinaryType, with sufficient information to either read it from
+ * disk or read it from the index.
+ */
+public final class BinaryTypeDescriptor {
+ public final char[] indexPath;
+ public final char[] fieldDescriptor;
+ public final char[] location;
+ public final char[] workspacePath;
+
+ /**
+ * Constructs a new descriptor
+ *
+ * @param location
+ * location where the archive (.jar or .class) can be found in the local filesystem
+ * @param fieldDescriptor
+ * field descriptor for the type (see the JVM specification)
+ * @param workspacePath
+ * location where the archive (.jar or class) can be found in the workspace. If it is not in the
+ * workspace, this is the path where it can be found on the local filesystem.
+ * @param indexPath
+ * index path for the new type (workspace-or-local path to jar optionally followed by a | and a relative
+ * path within the .jar)
+ */
+ public BinaryTypeDescriptor(char[] location, char[] fieldDescriptor, char[] workspacePath, char[] indexPath) {
+ super();
+ this.location = location;
+ this.fieldDescriptor = fieldDescriptor;
+ this.indexPath = indexPath;
+ this.workspacePath = workspacePath;
+ }
+
+ public boolean isInJarFile() {
+ return CharArrayUtils.indexOf(IDependent.JAR_FILE_ENTRY_SEPARATOR, this.indexPath) != -1;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
new file mode 100644
index 000000000..d6d3981a7
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/BinaryTypeFactory.java
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IClassFile;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaModelStatusConstants;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IDependent;
+import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
+import org.eclipse.jdt.internal.core.ClassFile;
+import org.eclipse.jdt.internal.core.JarPackageFragmentRoot;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.PackageFragment;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.db.IndexException;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
+import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdType;
+import org.eclipse.jdt.internal.core.nd.java.TypeRef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.Util;
+
+public class BinaryTypeFactory {
+ public static final class NotInIndexException extends Exception {
+ private static final long serialVersionUID = 2859848007651528256L;
+
+ public NotInIndexException() {
+ }
+ }
+
+ private final static char[] PACKAGE_INFO = "package-info".toCharArray(); //$NON-NLS-1$
+
+ /**
+ * Returns a descriptor for the given class within the given package fragment, or null if the fragment doesn't have
+ * a location on the filesystem.
+ */
+ private static BinaryTypeDescriptor createDescriptor(PackageFragment pkg, ClassFile classFile) {
+ String name = classFile.getName();
+ IJavaElement root = pkg.getParent();
+ IPath location = JavaIndex.getLocationForElement(root);
+ String entryName = Util.concatWith(pkg.names, classFile.getElementName(), '/');
+ char[] fieldDescriptor = CharArrayUtils.concat(new char[] { 'L' },
+ Util.concatWith(pkg.names, name, '/').toCharArray(), new char[] { ';' });
+ IPath workspacePath = root.getPath();
+ String indexPath;
+
+ if (location == null) {
+ return null;
+ }
+
+ if (root instanceof JarPackageFragmentRoot) {
+ // The old version returned this, but it doesn't conform to the spec on IBinaryType.getFileName():
+ indexPath = root.getHandleIdentifier() + IDependent.JAR_FILE_ENTRY_SEPARATOR + entryName;
+ // Version that conforms to the JavaDoc spec on IBinaryType.getFileName() -- note that this breaks
+ // InlineMethodTests in the JDT UI project. Need to investigate why before using it.
+ //indexPath = workspacePath.toString() + IDependent.JAR_FILE_ENTRY_SEPARATOR + entryName;
+ } else {
+ location = location.append(entryName);
+ indexPath = workspacePath.append(entryName).toString();
+ workspacePath = classFile.resource().getFullPath();
+ }
+
+ return new BinaryTypeDescriptor(location.toString().toCharArray(), fieldDescriptor,
+ workspacePath.toString().toCharArray(), indexPath.toCharArray());
+ }
+
+ public static BinaryTypeDescriptor createDescriptor(IClassFile classFile) {
+ ClassFile concreteClass = (ClassFile)classFile;
+ PackageFragment parent = (PackageFragment) classFile.getParent();
+
+ return createDescriptor(parent, concreteClass);
+ }
+
+ public static BinaryTypeDescriptor createDescriptor(IType type) {
+ return createDescriptor(type.getClassFile());
+ }
+
+ public static IBinaryType create(IClassFile classFile, IProgressMonitor monitor) throws JavaModelException, ClassFormatException {
+ BinaryTypeDescriptor descriptor = createDescriptor(classFile);
+ return readType(descriptor, monitor);
+ }
+
+ /**
+ * Reads the given binary type. If the type can be found in the index with a fingerprint that exactly matches
+ * the file on disk, the type is read from the index. Otherwise the type is read from disk. Returns null if
+ * no such type exists.
+ * @throws ClassFormatException
+ */
+ public static IBinaryType readType(BinaryTypeDescriptor descriptor,
+ IProgressMonitor monitor) throws JavaModelException, ClassFormatException {
+
+ if (JavaIndex.isEnabled()) {
+ try {
+ return readFromIndex(JavaIndex.getIndex(), descriptor, monitor);
+ } catch (NotInIndexException e) {
+ // fall back to reading the zip file, below
+ }
+ }
+
+ return rawReadType(descriptor, true);
+ }
+
+ /**
+ * Read the class file from disk, circumventing the index's cache. This should only be used by callers
+ * that need to read information from the class file which aren't present in the index (such as method bodies).
+ *
+ * @return the newly-created ClassFileReader or null if the given class file does not exist.
+ * @throws ClassFormatException if the class file existed but was corrupt
+ * @throws JavaModelException if unable to read the class file due to a transient failure
+ */
+ public static ClassFileReader rawReadType(BinaryTypeDescriptor descriptor, boolean fullyInitialize) throws JavaModelException, ClassFormatException {
+ if (descriptor == null) {
+ return null;
+ }
+ if (descriptor.isInJarFile()) {
+ ZipFile zip = null;
+ try {
+ zip = JavaModelManager.getJavaModelManager().getZipFile(new Path(new String(descriptor.workspacePath)));
+ char[] entryNameCharArray = CharArrayUtils.concat(
+ JavaNames.fieldDescriptorToBinaryName(descriptor.fieldDescriptor), SuffixConstants.SUFFIX_class);
+ String entryName = new String(entryNameCharArray);
+ ZipEntry ze = zip.getEntry(entryName);
+ if (ze != null) {
+ byte contents[];
+ try {
+ contents = org.eclipse.jdt.internal.compiler.util.Util.getZipEntryByteContent(ze, zip);
+ } catch (IOException ioe) {
+ throw new JavaModelException(ioe, IJavaModelStatusConstants.IO_EXCEPTION);
+ }
+ return new ClassFileReader(contents, descriptor.indexPath, fullyInitialize);
+ }
+ } catch (CoreException e) {
+ throw new JavaModelException(e);
+ } finally {
+ JavaModelManager.getJavaModelManager().closeZipFile(zip);
+ }
+ } else {
+ IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(new String(descriptor.workspacePath)));
+ byte[] contents = Util.getResourceContentsAsByteArray(file);
+ return new ClassFileReader(contents, file.getFullPath().toString().toCharArray(), fullyInitialize);
+ }
+ return null;
+ }
+
+ /**
+ * Tries to read the given IBinaryType from the index. The return value is lightweight and may be cached
+ * with minimal memory cost. Returns an IBinaryType if the type was found in the index and the index
+ * was up-to-date. Throws a NotInIndexException if the index does not contain an up-to-date cache of the
+ * requested file. Returns null if the index contains an up-to-date cache of the requested file and it was
+ * able to determine that the requested class does not exist in that file.
+ */
+ public static IBinaryType readFromIndex(JavaIndex index, BinaryTypeDescriptor descriptor, IProgressMonitor monitor) throws JavaModelException, NotInIndexException {
+ char[] className = JavaNames.fieldDescriptorToSimpleName(descriptor.fieldDescriptor);
+
+ // If the new index is enabled, check if we have this class file cached in the index already
+ char[] fieldDescriptor = descriptor.fieldDescriptor;
+
+ if (!CharArrayUtils.equals(PACKAGE_INFO, className)) {
+ Nd nd = index.getNd();
+
+ // We don't currently cache package-info files in the index
+ if (descriptor.location != null) {
+ // Acquire a read lock on the index
+ try (IReader lock = nd.acquireReadLock()) {
+ try {
+ TypeRef typeRef = TypeRef.create(nd, descriptor.location, fieldDescriptor);
+ NdType type = typeRef.get();
+
+ if (type == null) {
+ // If we couldn't find the type in the index, determine whether the cause is
+ // that the type is known not to exist or whether the resource just hasn't
+ // been indexed yet
+
+ NdResourceFile resourceFile = index.getResourceFile(descriptor.location);
+ if (index.isUpToDate(resourceFile)) {
+ return null;
+ }
+ throw new NotInIndexException();
+ }
+ NdResourceFile resourceFile = type.getResourceFile();
+ if (index.isUpToDate(resourceFile)) {
+ IndexBinaryType result = new IndexBinaryType(typeRef, descriptor.indexPath);
+
+ // We already have the database lock open and have located the element, so we may as
+ // well prefetch the inexpensive attributes.
+ result.initSimpleAttributes();
+
+ return result;
+ }
+ throw new NotInIndexException();
+ } catch (CoreException e) {
+ throw new JavaModelException(e);
+ }
+ } catch (IndexException e) {
+ // Index corrupted. Rebuild it.
+ index.rebuildIndex();
+ }
+ }
+ }
+
+ throw new NotInIndexException();
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/ITypeAnnotationBuilder.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/ITypeAnnotationBuilder.java
new file mode 100644
index 000000000..39e950133
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/ITypeAnnotationBuilder.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public interface ITypeAnnotationBuilder {
+ ITypeAnnotationBuilder toField();
+ ITypeAnnotationBuilder toThrows(int rank);
+ ITypeAnnotationBuilder toTypeArgument(int rank);
+ ITypeAnnotationBuilder toMethodParameter(short index);
+ ITypeAnnotationBuilder toSupertype(short index);
+ ITypeAnnotationBuilder toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank);
+ ITypeAnnotationBuilder toTypeBound(short boundIndex);
+ ITypeAnnotationBuilder toTypeParameter(boolean isClassTypeParameter, int rank);
+ ITypeAnnotationBuilder toMethodReturn();
+ ITypeAnnotationBuilder toReceiver();
+ ITypeAnnotationBuilder toWildcardBound();
+ ITypeAnnotationBuilder toNextArrayDimension();
+ ITypeAnnotationBuilder toNextNestedType();
+
+ IBinaryTypeAnnotation build(IBinaryAnnotation annotation);
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryField.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryField.java
new file mode 100644
index 000000000..3c1f7634c
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryField.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+
+public class IndexBinaryField implements IBinaryField {
+ private int modifiers;
+ private IBinaryAnnotation[] annotations;
+ private IBinaryTypeAnnotation[] typeAnnotations;
+ private Constant constant;
+ private char[] genericSignature;
+ private char[] name;
+ private long tagBits;
+ private char[] typeName;
+
+ public IndexBinaryField(IBinaryAnnotation[] annotations, Constant constant, char[] genericSignature, int modifiers,
+ char[] name, long tagBits, IBinaryTypeAnnotation[] typeAnnotations, char[] fieldDescriptor) {
+ super();
+ this.modifiers = modifiers;
+ this.annotations = annotations;
+ this.typeAnnotations = typeAnnotations;
+ this.constant = constant;
+ this.genericSignature = genericSignature;
+ this.name = name;
+ this.tagBits = tagBits;
+ this.typeName = fieldDescriptor;
+ }
+
+ @Override
+ public int getModifiers() {
+ return this.modifiers;
+ }
+
+ @Override
+ public IBinaryAnnotation[] getAnnotations() {
+ return this.annotations;
+ }
+
+ @Override
+ public IBinaryTypeAnnotation[] getTypeAnnotations() {
+ return this.typeAnnotations;
+ }
+
+ @Override
+ public Constant getConstant() {
+ return this.constant;
+ }
+
+ @Override
+ public char[] getGenericSignature() {
+ return this.genericSignature;
+ }
+
+ @Override
+ public char[] getName() {
+ return this.name;
+ }
+
+ @Override
+ public long getTagBits() {
+ return this.tagBits;
+ }
+
+ @Override
+ public char[] getTypeName() {
+ return this.typeName;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryMethod.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryMethod.java
new file mode 100644
index 000000000..5ce3c4cd5
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryMethod.java
@@ -0,0 +1,182 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import org.eclipse.jdt.internal.compiler.classfmt.BinaryTypeFormatter;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public final class IndexBinaryMethod implements IBinaryMethod {
+ private int modifiers;
+ private boolean isConstructor;
+ private char[][] argumentNames;
+ private IBinaryAnnotation[] annotations;
+ private Object defaultValue;
+ private char[][] exceptionTypeNames;
+ private char[] genericSignature;
+ private char[] methodDescriptor;
+ private IBinaryAnnotation[][] parameterAnnotations;
+ private char[] selector;
+ private long tagBits;
+ private boolean isClInit;
+ private IBinaryTypeAnnotation[] typeAnnotations;
+
+ public static IndexBinaryMethod create() {
+ return new IndexBinaryMethod();
+ }
+
+ public IndexBinaryMethod setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ return this;
+ }
+
+ public IndexBinaryMethod setIsConstructor(boolean isConstructor) {
+ this.isConstructor = isConstructor;
+ return this;
+ }
+
+ public IndexBinaryMethod setArgumentNames(char[][] argumentNames) {
+ this.argumentNames = argumentNames;
+ return this;
+ }
+
+ public IndexBinaryMethod setAnnotations(IBinaryAnnotation[] annotations) {
+ this.annotations = annotations;
+ return this;
+ }
+
+ public IndexBinaryMethod setDefaultValue(Object defaultValue) {
+ this.defaultValue = defaultValue;
+ return this;
+ }
+
+ public IndexBinaryMethod setExceptionTypeNames(char[][] exceptionTypeNames) {
+ this.exceptionTypeNames = exceptionTypeNames;
+ return this;
+ }
+
+ public IndexBinaryMethod setGenericSignature(char[] genericSignature) {
+ this.genericSignature = genericSignature;
+ return this;
+ }
+
+ public IndexBinaryMethod setMethodDescriptor(char[] methodDescriptor) {
+ this.methodDescriptor = methodDescriptor;
+ return this;
+ }
+
+ public IndexBinaryMethod setParameterAnnotations(IBinaryAnnotation[][] parameterAnnotations) {
+ this.parameterAnnotations = parameterAnnotations;
+ return this;
+ }
+
+ public IndexBinaryMethod setSelector(char[] selector) {
+ this.selector = selector;
+ return this;
+ }
+
+ public IndexBinaryMethod setTagBits(long tagBits) {
+ this.tagBits = tagBits;
+ return this;
+ }
+
+ public IndexBinaryMethod setIsClInit(boolean isClInit) {
+ this.isClInit = isClInit;
+ return this;
+ }
+
+ public IndexBinaryMethod setTypeAnnotations(IBinaryTypeAnnotation[] typeAnnotations) {
+ this.typeAnnotations = typeAnnotations;
+ return this;
+ }
+
+ @Override
+ public int getModifiers() {
+ return this.modifiers;
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return this.isConstructor;
+ }
+
+ @Override
+ public char[][] getArgumentNames() {
+ return this.argumentNames;
+ }
+
+ @Override
+ public IBinaryAnnotation[] getAnnotations() {
+ return this.annotations;
+ }
+
+ @Override
+ public Object getDefaultValue() {
+ return this.defaultValue;
+ }
+
+ @Override
+ public char[][] getExceptionTypeNames() {
+ return this.exceptionTypeNames;
+ }
+
+ @Override
+ public char[] getGenericSignature() {
+ return this.genericSignature;
+ }
+
+ @Override
+ public char[] getMethodDescriptor() {
+ return this.methodDescriptor;
+ }
+
+ @Override
+ public IBinaryAnnotation[] getParameterAnnotations(int index, char[] classFileName) {
+ if (this.parameterAnnotations == null || this.parameterAnnotations.length <= index) {
+ return null;
+ }
+ return this.parameterAnnotations[index];
+ }
+
+ @Override
+ public int getAnnotatedParametersCount() {
+ if (this.parameterAnnotations == null) {
+ return 0;
+ }
+ return this.parameterAnnotations.length;
+ }
+
+ @Override
+ public char[] getSelector() {
+ return this.selector;
+ }
+
+ @Override
+ public long getTagBits() {
+ return this.tagBits;
+ }
+
+ @Override
+ public boolean isClinit() {
+ return this.isClInit;
+ }
+
+ @Override
+ public IBinaryTypeAnnotation[] getTypeAnnotations() {
+ return this.typeAnnotations;
+ }
+
+ @Override
+ public String toString() {
+ return BinaryTypeFormatter.methodToString(this);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryNestedType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryNestedType.java
new file mode 100644
index 000000000..40d8a5402
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryNestedType.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
+
+public class IndexBinaryNestedType implements IBinaryNestedType {
+ private char[] enclosingTypeName;
+ private char[] name;
+ private int modifiers;
+
+ public IndexBinaryNestedType(char[] name, char[] enclosingTypeName, int modifiers) {
+ super();
+ this.name = name;
+ this.enclosingTypeName = enclosingTypeName;
+ this.modifiers = modifiers;
+ }
+
+ @Override
+ public char[] getEnclosingTypeName() {
+ return this.enclosingTypeName;
+ }
+
+ @Override
+ public int getModifiers() {
+ return this.modifiers;
+ }
+
+ @Override
+ public char[] getName() {
+ return this.name;
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryType.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryType.java
new file mode 100644
index 000000000..aaa7576a2
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryType.java
@@ -0,0 +1,672 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.classfmt.BinaryTypeFormatter;
+import org.eclipse.jdt.internal.compiler.classfmt.ElementValuePairInfo;
+import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.compiler.env.ClassSignature;
+import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.env.ITypeAnnotationWalker;
+import org.eclipse.jdt.internal.compiler.impl.Constant;
+import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.ExternalAnnotationStatus;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.db.IString;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotation;
+import org.eclipse.jdt.internal.core.nd.java.NdAnnotationValuePair;
+import org.eclipse.jdt.internal.core.nd.java.NdConstant;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantAnnotation;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantArray;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantClass;
+import org.eclipse.jdt.internal.core.nd.java.NdConstantEnum;
+import org.eclipse.jdt.internal.core.nd.java.NdMethod;
+import org.eclipse.jdt.internal.core.nd.java.NdMethodException;
+import org.eclipse.jdt.internal.core.nd.java.NdMethodId;
+import org.eclipse.jdt.internal.core.nd.java.NdMethodParameter;
+import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeAnnotation;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeInterface;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeParameter;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeSignature;
+import org.eclipse.jdt.internal.core.nd.java.NdVariable;
+import org.eclipse.jdt.internal.core.nd.java.TypeRef;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.util.CharArrayBuffer;
+
+/**
+ * Implementation of {@link IBinaryType} that reads all its content from the index
+ */
+public class IndexBinaryType implements IBinaryType {
+ private final TypeRef typeRef;
+
+ private boolean simpleAttributesInitialized;
+ private char[] enclosingMethod;
+ private char[] enclosingType;
+ private char[] fileName;
+ private char[] superclassName;
+ private int modifiers;
+ private boolean isAnonymous;
+ private boolean isLocal;
+ private boolean isMember;
+
+ private long tagBits;
+
+ private char[] binaryTypeName;
+
+ private static final IBinaryAnnotation[] NO_ANNOTATIONS = new IBinaryAnnotation[0];
+ private static final int[] NO_PATH = new int[0];
+
+ public IndexBinaryType(TypeRef type, char[] indexPath) {
+ this.typeRef = type;
+ this.fileName = indexPath;
+ }
+
+ public boolean exists() {
+ return this.typeRef.get() != null;
+ }
+
+ @Override
+ public int getModifiers() {
+ initSimpleAttributes();
+
+ return this.modifiers;
+ }
+
+ @Override
+ public boolean isBinaryType() {
+ return true;
+ }
+
+ @Override
+ public char[] getFileName() {
+ return this.fileName;
+ }
+
+ @Override
+ public IBinaryAnnotation[] getAnnotations() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ return toAnnotationArray(this.typeRef.get().getAnnotations());
+ } else {
+ return NO_ANNOTATIONS;
+ }
+ }
+ }
+
+ private static IBinaryAnnotation[] toAnnotationArray(List<? extends NdAnnotation> annotations) {
+ if (annotations.isEmpty()) {
+ return NO_ANNOTATIONS;
+ }
+ IBinaryAnnotation[] result = new IBinaryAnnotation[annotations.size()];
+
+ for (int idx = 0; idx < result.length; idx++) {
+ result[idx] = createBinaryAnnotation(annotations.get(idx));
+ }
+ return result;
+ }
+
+ @Override
+ public IBinaryTypeAnnotation[] getTypeAnnotations() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ return createBinaryTypeAnnotations(type.getTypeAnnotations());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public char[] getEnclosingMethod() {
+ initSimpleAttributes();
+
+ return this.enclosingMethod;
+ }
+
+ @Override
+ public char[] getEnclosingTypeName() {
+ initSimpleAttributes();
+
+ return this.enclosingType;
+ }
+
+ @Override
+ public IBinaryField[] getFields() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ List<NdVariable> variables = type.getVariables();
+
+ if (variables.isEmpty()) {
+ return null;
+ }
+
+ IBinaryField[] result = new IBinaryField[variables.size()];
+ for (int fieldIdx = 0; fieldIdx < variables.size(); fieldIdx++) {
+ result[fieldIdx] = createBinaryField(variables.get(fieldIdx));
+ }
+ return result;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ public char[] getGenericSignature() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ if (!type.getFlag(NdType.FLG_GENERIC_SIGNATURE_PRESENT)) {
+ return null;
+ }
+ CharArrayBuffer buffer = new CharArrayBuffer();
+ NdTypeParameter.getSignature(buffer, type.getTypeParameters());
+ NdTypeSignature superclass = type.getSuperclass();
+ if (superclass != null) {
+ superclass.getSignature(buffer);
+ }
+ for (NdTypeInterface nextInterface : type.getInterfaces()) {
+ nextInterface.getInterface().getSignature(buffer);
+ }
+ return buffer.getContents();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ public char[][] getInterfaceNames() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ List<NdTypeInterface> interfaces = type.getInterfaces();
+
+ if (interfaces.isEmpty()) {
+ return null;
+ }
+
+ char[][] result = new char[interfaces.size()][];
+ for (int idx = 0; idx < interfaces.size(); idx++) {
+ NdTypeSignature nextInterface = interfaces.get(idx).getInterface();
+
+ result[idx] = nextInterface.getRawType().getBinaryName();
+ }
+ return result;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ public IBinaryNestedType[] getMemberTypes() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ List<NdType> declaredTypes = type.getTypeId().getDeclaredTypes();
+ if (declaredTypes.isEmpty()) {
+ return null;
+ }
+
+ NdResourceFile resFile = type.getResourceFile();
+ IString javaRoot = resFile.getPackageFragmentRoot();
+
+ // Filter out all the declared types which are at different java roots (only keep the ones belonging
+ // to the same .jar file or to another .class file in the same folder).
+ List<IBinaryNestedType> result = new ArrayList<>();
+ for (NdType next : declaredTypes) {
+ NdResourceFile nextResFile = next.getResourceFile();
+
+ if (nextResFile.getPackageFragmentRoot().compare(javaRoot, true) == 0) {
+ result.add(createBinaryNestedType(next));
+ }
+ }
+ return result.isEmpty() ? null : result.toArray(new IBinaryNestedType[result.size()]);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private IBinaryNestedType createBinaryNestedType(NdType next) {
+ return new IndexBinaryNestedType(next.getTypeId().getBinaryName(), next.getDeclaringType().getBinaryName(),
+ next.getModifiers());
+ }
+
+ @Override
+ public IBinaryMethod[] getMethods() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ List<NdMethod> methods = type.getMethods();
+
+ if (methods.isEmpty()) {
+ return null;
+ }
+
+ IBinaryMethod[] result = new IBinaryMethod[methods.size()];
+ for (int idx = 0; idx < result.length; idx++) {
+ result[idx] = createBinaryMethod(methods.get(idx));
+ }
+
+ return result;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ public char[][][] getMissingTypeNames() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ IString string = type.getMissingTypeNames();
+ if (string.length() == 0) {
+ return null;
+ }
+ char[] missingTypeNames = string.getChars();
+ char[][] paths = CharOperation.splitOn(',', missingTypeNames);
+ char[][][] result = new char[paths.length][][];
+ for (int idx = 0; idx < paths.length; idx++) {
+ result[idx] = CharOperation.splitOn('/', paths[idx]);
+ }
+ return result;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ public char[] getName() {
+ initSimpleAttributes();
+
+ return this.binaryTypeName;
+ }
+
+ @Override
+ public char[] getSourceName() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ return type.getSourceName();
+ } else {
+ return new char[0];
+ }
+ }
+ }
+
+ @Override
+ public char[] getSuperclassName() {
+ initSimpleAttributes();
+
+ return this.superclassName;
+ }
+
+ @Override
+ public long getTagBits() {
+ initSimpleAttributes();
+
+ return this.tagBits;
+ }
+
+ @Override
+ public boolean isAnonymous() {
+ initSimpleAttributes();
+
+ return this.isAnonymous;
+ }
+
+ @Override
+ public boolean isLocal() {
+ initSimpleAttributes();
+
+ return this.isLocal;
+ }
+
+ @Override
+ public boolean isMember() {
+ initSimpleAttributes();
+
+ return this.isMember;
+ }
+
+ @Override
+ public char[] sourceFileName() {
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ char[] result = type.getSourceFileName().getChars();
+ if (result.length == 0) {
+ return null;
+ }
+ return result;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
+ public ITypeAnnotationWalker enrichWithExternalAnnotationsFor(ITypeAnnotationWalker walker, Object member,
+ LookupEnvironment environment) {
+ return walker;
+ }
+
+ private IBinaryMethod createBinaryMethod(NdMethod ndMethod) {
+ NdMethodId methodId = ndMethod.getMethodId();
+
+ return IndexBinaryMethod.create().setAnnotations(toAnnotationArray(ndMethod.getAnnotations()))
+ .setModifiers(ndMethod.getModifiers()).setIsConstructor(methodId.isConstructor())
+ .setArgumentNames(getArgumentNames(ndMethod)).setDefaultValue(unpackValue(ndMethod.getDefaultValue()))
+ .setExceptionTypeNames(getExceptionTypeNames(ndMethod))
+ .setGenericSignature(getGenericSignatureFor(ndMethod))
+ .setMethodDescriptor(methodId.getMethodDescriptor())
+ .setParameterAnnotations(getParameterAnnotations(ndMethod))
+ .setSelector(ndMethod.getMethodId().getSelector()).setTagBits(ndMethod.getTagBits())
+ .setIsClInit(methodId.isClInit()).setTypeAnnotations(createBinaryTypeAnnotations(ndMethod.getTypeAnnotations()));
+ }
+
+ private static IBinaryTypeAnnotation[] createBinaryTypeAnnotations(List<? extends NdTypeAnnotation> typeAnnotations) {
+ if (typeAnnotations.isEmpty()) {
+ return null;
+ }
+ IBinaryTypeAnnotation[] result = new IBinaryTypeAnnotation[typeAnnotations.size()];
+ int idx = 0;
+ for (NdTypeAnnotation next : typeAnnotations) {
+ IBinaryAnnotation annotation = createBinaryAnnotation(next);
+ int[] typePath = getTypePath(next.getTypePath());
+ int info = 0;
+ int info2 = 0;
+ switch (next.getTargetType()) {
+ case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER:
+ case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER:
+ info = next.getTargetInfoArg0();
+ break;
+ case AnnotationTargetTypeConstants.CLASS_EXTENDS:
+ info = next.getTarget();
+ break;
+ case AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER_BOUND:
+ case AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER_BOUND:
+ info = next.getTargetInfoArg0();
+ info2 = next.getTargetInfoArg1();
+ break;
+ case AnnotationTargetTypeConstants.FIELD:
+ case AnnotationTargetTypeConstants.METHOD_RETURN:
+ case AnnotationTargetTypeConstants.METHOD_RECEIVER:
+ break;
+ case AnnotationTargetTypeConstants.METHOD_FORMAL_PARAMETER :
+ info = next.getTargetInfoArg0();
+ break;
+ case AnnotationTargetTypeConstants.THROWS :
+ info = next.getTarget();
+ break;
+
+ default:
+ throw new IllegalStateException("Target type not handled " + next.getTargetType()); //$NON-NLS-1$
+ }
+ result[idx++] = new IndexBinaryTypeAnnotation(next.getTargetType(), info, info2, typePath, annotation);
+ }
+ return result;
+ }
+
+ private static int[] getTypePath(byte[] typePath) {
+ if (typePath.length == 0) {
+ return NO_PATH;
+ }
+ int[] result = new int[typePath.length];
+ for (int idx = 0; idx < typePath.length; idx++) {
+ result[idx] = typePath[idx];
+ }
+ return result;
+ }
+
+ private static char[] getGenericSignatureFor(NdMethod method) {
+ if (!method.hasAllFlags(NdMethod.FLG_GENERIC_SIGNATURE_PRESENT)) {
+ return null;
+ }
+ CharArrayBuffer result = new CharArrayBuffer();
+ method.getGenericSignature(result, method.hasAllFlags(NdMethod.FLG_THROWS_SIGNATURE_PRESENT));
+ return result.getContents();
+ }
+
+ private char[][] getArgumentNames(NdMethod ndMethod) {
+ // Unlike what its JavaDoc says, IBinaryType returns an empty array if no argument names are available, so
+ // we replicate this weird undocumented corner case here.
+ char[][] result = ndMethod.getParameterNames();
+ int lastNonEmpty = -1;
+ for (int idx = 0; idx < result.length; idx++) {
+ if (result[idx] != null && result[idx].length != 0) {
+ lastNonEmpty = idx;
+ }
+ }
+
+ if (lastNonEmpty != result.length - 1) {
+ char[][] newResult = new char[lastNonEmpty + 1][];
+ System.arraycopy(result, 0, newResult, 0, lastNonEmpty + 1);
+ return newResult;
+ }
+ return result;
+ }
+
+ private IBinaryAnnotation[][] getParameterAnnotations(NdMethod ndMethod) {
+ List<NdMethodParameter> parameters = ndMethod.getMethodParameters();
+ if (parameters.isEmpty()) {
+ return null;
+ }
+
+ IBinaryAnnotation[][] result = new IBinaryAnnotation[parameters.size()][];
+ for (int idx = 0; idx < parameters.size(); idx++) {
+ NdMethodParameter next = parameters.get(idx);
+
+ result[idx] = toAnnotationArray(next.getAnnotations());
+ }
+
+ // int newLength = result.length;
+ // while (newLength > 0 && result[newLength - 1] == null) {
+ // --newLength;
+ // }
+ //
+ // if (newLength < result.length) {
+ // if (newLength == 0) {
+ // return null;
+ // }
+ // IBinaryAnnotation[][] newResult = new IBinaryAnnotation[newLength][];
+ // System.arraycopy(result, 0, newResult, 0, newLength);
+ // result = newResult;
+ // }
+
+ return result;
+ }
+
+ private char[][] getExceptionTypeNames(NdMethod ndMethod) {
+ List<NdMethodException> exceptions = ndMethod.getExceptions();
+
+ // Although the JavaDoc for IBinaryMethod says that the exception list will be null if empty,
+ // the implementation in MethodInfo returns an empty array rather than null. We copy the
+ // same behavior here in case something is relying on it. Uncomment the following if the "null"
+ // version is deemed correct.
+
+ // if (exceptions.isEmpty()) {
+ // return null;
+ // }
+
+ char[][] result = new char[exceptions.size()][];
+ for (int idx = 0; idx < exceptions.size(); idx++) {
+ NdMethodException next = exceptions.get(idx);
+
+ result[idx] = next.getExceptionType().getRawType().getBinaryName();
+ }
+ return result;
+ }
+
+ public static IBinaryField createBinaryField(NdVariable ndVariable) {
+ char[] name = ndVariable.getName().getChars();
+ Constant constant = null;
+ NdConstant ndConstant = ndVariable.getConstant();
+ if (ndConstant != null) {
+ constant = ndConstant.getConstant();
+ }
+ if (constant == null) {
+ constant = Constant.NotAConstant;
+ }
+
+ NdTypeSignature type = ndVariable.getType();
+
+ IBinaryTypeAnnotation[] typeAnnotationArray = createBinaryTypeAnnotations(ndVariable.getTypeAnnotations());
+
+ IBinaryAnnotation[] annotations = toAnnotationArray(ndVariable.getAnnotations());
+
+ CharArrayBuffer signature = new CharArrayBuffer();
+ if (ndVariable.hasVariableFlag(NdVariable.FLG_GENERIC_SIGNATURE_PRESENT)) {
+ type.getSignature(signature);
+ }
+
+ long tagBits = ndVariable.getTagBits();
+ return new IndexBinaryField(annotations, constant, signature.getContents(), ndVariable.getModifiers(), name,
+ tagBits, typeAnnotationArray, type.getRawType().getFieldDescriptor().getChars());
+ }
+
+ public static IBinaryAnnotation createBinaryAnnotation(NdAnnotation ndAnnotation) {
+ List<NdAnnotationValuePair> elementValuePairs = ndAnnotation.getElementValuePairs();
+
+ final IBinaryElementValuePair[] resultingPair = new IBinaryElementValuePair[elementValuePairs.size()];
+
+ for (int idx = 0; idx < elementValuePairs.size(); idx++) {
+ NdAnnotationValuePair next = elementValuePairs.get(idx);
+
+ resultingPair[idx] = new ElementValuePairInfo(next.getName().getChars(), unpackValue(next.getValue()));
+ }
+
+ final char[] binaryName = JavaNames.fieldDescriptorToBinaryName(
+ ndAnnotation.getType().getRawType().getFieldDescriptor().getChars());
+
+ return new IBinaryAnnotation() {
+ @Override
+ public char[] getTypeName() {
+ return binaryName;
+ }
+
+ @Override
+ public IBinaryElementValuePair[] getElementValuePairs() {
+ return resultingPair;
+ }
+
+ @Override
+ public String toString() {
+ return BinaryTypeFormatter.annotationToString(this);
+ }
+ };
+ }
+
+ public void initSimpleAttributes() {
+ if (!this.simpleAttributesInitialized) {
+ this.simpleAttributesInitialized = true;
+
+ try (IReader rl = this.typeRef.lock()) {
+ NdType type = this.typeRef.get();
+ if (type != null) {
+ NdMethodId methodId = type.getDeclaringMethod();
+
+ if (methodId != null) {
+ char[] methodName = methodId.getMethodName().getChars();
+ int startIdx = CharArrayUtils.lastIndexOf('#', methodName);
+ this.enclosingMethod = CharArrayUtils.subarray(methodName, startIdx + 1);
+ this.enclosingType = CharArrayUtils.subarray(methodName, 1, startIdx);
+ } else {
+ NdTypeId typeId = type.getDeclaringType();
+
+ if (typeId != null) {
+ this.enclosingType = typeId.getBinaryName();
+ }
+ }
+
+ this.modifiers = type.getModifiers();
+ this.isAnonymous = type.isAnonymous();
+ this.isLocal = type.isLocal();
+ this.isMember = type.isMember();
+ this.tagBits = type.getTagBits();
+
+ NdTypeSignature superclass = type.getSuperclass();
+ if (superclass != null) {
+ this.superclassName = superclass.getRawType().getBinaryName();
+ } else {
+ this.superclassName = null;
+ }
+
+ this.binaryTypeName = JavaNames.fieldDescriptorToBinaryName(type.getFieldDescriptor().getChars());
+ } else {
+ this.binaryTypeName = JavaNames.fieldDescriptorToBinaryName(this.typeRef.getFieldDescriptor());
+ }
+ }
+ }
+ }
+
+ private static Object unpackValue(NdConstant value) {
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof NdConstantAnnotation) {
+ NdConstantAnnotation annotation = (NdConstantAnnotation) value;
+
+ return createBinaryAnnotation(annotation.getValue());
+ }
+ if (value instanceof NdConstantArray) {
+ NdConstantArray array = (NdConstantArray) value;
+
+ List<NdConstant> arrayContents = array.getValue();
+
+ Object[] result = new Object[arrayContents.size()];
+ for (int idx = 0; idx < arrayContents.size(); idx++) {
+ result[idx] = unpackValue(arrayContents.get(idx));
+ }
+ return result;
+ }
+ if (value instanceof NdConstantEnum) {
+ NdConstantEnum ndConstantEnum = (NdConstantEnum) value;
+
+ NdTypeSignature signature = ndConstantEnum.getType();
+
+ return new EnumConstantSignature(signature.getRawType().getBinaryName(), ndConstantEnum.getValue());
+ }
+ if (value instanceof NdConstantClass) {
+ NdConstantClass constant = (NdConstantClass) value;
+
+ return new ClassSignature(constant.getValue().getRawType().getBinaryName());
+ }
+
+ return value.getConstant();
+ }
+
+ @Override
+ public ExternalAnnotationStatus getExternalAnnotationStatus() {
+ return ExternalAnnotationStatus.NOT_EEA_CONFIGURED;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryTypeAnnotation.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryTypeAnnotation.java
new file mode 100644
index 000000000..521e17bd4
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/IndexBinaryTypeAnnotation.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import org.eclipse.jdt.internal.compiler.classfmt.BinaryTypeFormatter;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public class IndexBinaryTypeAnnotation implements IBinaryTypeAnnotation {
+ private int targetType;
+
+ // info is used in different ways:
+ // TargetType 0x00: CLASS_TYPE_PARAMETER: type parameter index
+ // TargetType 0x01: METHOD_TYPE_PARAMETER: type parameter index
+ // TargetType 0x10: CLASS_EXTENDS: supertype index (-1 = superclass, 0..N superinterface)
+ // TargetType 0x11: CLASS_TYPE_PARAMETER_BOUND: type parameter index
+ // TargetType 0x12: METHOD_TYPE_PARAMETER_BOUND: type parameter index
+ // TargetType 0x16: METHOD_FORMAL_PARAMETER: method formal parameter index
+ // TargetType 0x17: THROWS: throws type index
+ private int info;
+
+ // TargetType 0x11: CLASS_TYPE_PARAMETER_BOUND: bound index
+ // TargetType 0x12: METHOD_TYPE_PARAMETER_BOUND: bound index
+ private int info2;
+
+
+ private int[] typePath;
+ private IBinaryAnnotation annotation;
+
+ public IndexBinaryTypeAnnotation(int targetType, int info, int info2, int[] typePath, IBinaryAnnotation annotation) {
+ this.targetType = targetType;
+ this.info = info;
+ this.info2 = info2;
+ this.typePath = typePath;
+ this.annotation = annotation;
+ }
+
+ @Override
+ public IBinaryAnnotation getAnnotation() {
+ return this.annotation;
+ }
+
+ @Override
+ public int getTargetType() {
+ return this.targetType;
+ }
+
+ @Override
+ public int[] getTypePath() {
+ return this.typePath;
+ }
+
+ @Override
+ public int getSupertypeIndex() {
+ return this.info;
+ }
+
+ @Override
+ public int getTypeParameterIndex() {
+ return this.info;
+}
+
+ @Override
+ public int getBoundIndex() {
+ return this.info2;
+ }
+
+ @Override
+ public int getMethodFormalParameterIndex() {
+ return this.info;
+ }
+
+ @Override
+ public int getThrowsTypeIndex() {
+ return this.info;
+ }
+
+ @Override
+ public String toString() {
+ return BinaryTypeFormatter.annotationToString(this);
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/TypeAnnotationBuilder.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/TypeAnnotationBuilder.java
new file mode 100644
index 000000000..0b8d7d4ed
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/java/model/TypeAnnotationBuilder.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.java.model;
+
+import org.eclipse.jdt.internal.compiler.codegen.AnnotationTargetTypeConstants;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+
+public class TypeAnnotationBuilder implements ITypeAnnotationBuilder {
+ TypeAnnotationBuilder parent;
+ int kind;
+ int index;
+ int length;
+ int target;
+ int targetParameter;
+ int targetParameter2;
+
+ private TypeAnnotationBuilder(TypeAnnotationBuilder parent, int kind, int index,
+ int length, int nextTarget, int nextTargetParameter, int nextTargetParameter2) {
+ super();
+ this.parent = parent;
+ this.kind = kind;
+ this.index = index;
+ this.length = length;
+ this.target = nextTarget;
+ this.targetParameter = nextTargetParameter;
+ this.targetParameter2 = nextTargetParameter2;
+ }
+
+ public static TypeAnnotationBuilder create() {
+ return new TypeAnnotationBuilder(null, 0, 0, 0, -1, -1, -1);
+ }
+
+ private TypeAnnotationBuilder walk(int nextKind, int nextIndex) {
+ return new TypeAnnotationBuilder(this, nextKind, nextIndex, this.length+1, this.target, this.targetParameter, this.targetParameter2);
+ }
+
+ private TypeAnnotationBuilder toTarget(int newTarget) {
+ return new TypeAnnotationBuilder(this.parent, this.kind, this.index, this.length, newTarget, this.targetParameter, this.targetParameter2);
+ }
+
+ private TypeAnnotationBuilder toTarget(int newTarget, int parameter) {
+ return new TypeAnnotationBuilder(this.parent, this.kind, this.index, this.length, newTarget, parameter, this.targetParameter2);
+ }
+
+ private TypeAnnotationBuilder toTarget2(int parameter) {
+ return new TypeAnnotationBuilder(this.parent, this.kind, this.index, this.length, this.target, this.targetParameter, parameter);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toField() {
+ return toTarget(AnnotationTargetTypeConstants.FIELD);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toMethodReturn() {
+ return toTarget(AnnotationTargetTypeConstants.METHOD_RETURN);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toReceiver() {
+ return toTarget(AnnotationTargetTypeConstants.METHOD_RECEIVER);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toTypeParameter(boolean isClassTypeParameter, int rank) {
+ int targetType = isClassTypeParameter ? AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER
+ : AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER;
+ return toTarget(targetType, rank);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toTypeParameterBounds(boolean isClassTypeParameter, int parameterRank) {
+ int targetType = isClassTypeParameter ?
+ AnnotationTargetTypeConstants.CLASS_TYPE_PARAMETER_BOUND : AnnotationTargetTypeConstants.METHOD_TYPE_PARAMETER_BOUND;
+
+ return toTarget(targetType, parameterRank);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toTypeBound(short boundIndex) {
+ return toTarget2(boundIndex);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toSupertype(short superTypeIndex) {
+ return toTarget(AnnotationTargetTypeConstants.CLASS_EXTENDS, superTypeIndex);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toMethodParameter(short parameterIndex) {
+ return toTarget(AnnotationTargetTypeConstants.METHOD_FORMAL_PARAMETER, parameterIndex);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toThrows(int rank) {
+ return toTarget(AnnotationTargetTypeConstants.THROWS, rank);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toTypeArgument(int rank) {
+ return walk(AnnotationTargetTypeConstants.TYPE_ARGUMENT, rank);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toWildcardBound() {
+ return walk(AnnotationTargetTypeConstants.WILDCARD_BOUND, 0);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toNextArrayDimension() {
+ return walk(AnnotationTargetTypeConstants.NEXT_ARRAY_DIMENSION, 0);
+ }
+
+ @Override
+ public ITypeAnnotationBuilder toNextNestedType() {
+ return walk(AnnotationTargetTypeConstants.NEXT_NESTED_TYPE, 0);
+ }
+
+ @Override
+ public IBinaryTypeAnnotation build(IBinaryAnnotation annotation) {
+ return new IndexBinaryTypeAnnotation(this.target, this.targetParameter, this.targetParameter2, getTypePath(), annotation);
+ }
+
+ private int[] getTypePath() {
+ if (this.length == 0) {
+ return IBinaryTypeAnnotation.NO_TYPE_PATH;
+ }
+
+ int[] result = new int[this.length * 2];
+
+ TypeAnnotationBuilder next = this;
+ while (next != null && next.length > 0) {
+ int writeIdx = (next.length - 1) * 2;
+ result[writeIdx] = next.kind;
+ result[writeIdx + 1] = next.index;
+ next = next.parent;
+ }
+
+ return result;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayMap.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayMap.java
new file mode 100644
index 000000000..eed0a5a74
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayMap.java
@@ -0,0 +1,312 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2016 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+
+/**
+ * Provides functionality similar to a Map, with the feature that char arrays
+ * and sections of char arrays (known as slices) may be used as keys.
+ *
+ * This class is useful because small pieces of an existing large char[] buffer
+ * can be directly used as map keys. This avoids the need to create many String
+ * objects as would normally be needed as keys in a standard java.util.Map.
+ * Thus performance is improved in the CDT core.
+ *
+ * Most methods are overloaded with two versions, one that uses a
+ * section of a char[] as the key (a slice), and one that uses
+ * the entire char[] as the key.
+ *
+ * This class is intended as a replacement for CharArrayObjectMap.
+ *
+ * ex:
+ * char[] key = "one two three".toCharArray();
+ * map.put(key, 4, 3, new Integer(99));
+ * map.get(key, 4, 3); // returns 99
+ * map.get("two".toCharArray()); // returns 99
+ *
+ * @author Mike Kucera
+ *
+ * @param <V>
+ */
+public final class CharArrayMap<V> {
+
+ /**
+ * Wrapper class used as keys in the map. The purpose
+ * of this class is to provide implementations of
+ * equals() and hashCode() that operate on array slices.
+ *
+ * This class is private so it is assumed that the arguments
+ * passed to the constructor are legal.
+ */
+ private static final class Key implements Comparable<Key>{
+ final char[] buffer;
+ final int start;
+ final int length;
+
+ public Key(char[] buffer, int start, int length) {
+ this.buffer = buffer;
+ this.length = length;
+ this.start = start;
+ }
+
+ /**
+ * @throws NullPointerException if buffer is null
+ */
+ public Key(char[] buffer) {
+ this.buffer = buffer;
+ this.length = buffer.length; // throws NPE
+ this.start = 0;
+ }
+
+ @Override
+ public boolean equals(Object x) {
+ if(this == x)
+ return true;
+ if(!(x instanceof Key))
+ return false;
+
+ Key k = (Key) x;
+ if(this.length != k.length)
+ return false;
+
+ for(int i = this.start, j = k.start; i < this.length; i++, j++) {
+ if(this.buffer[i] != k.buffer[j]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ for(int i = this.start; i < this.start+this.length; i++) {
+ result = 37 * result + this.buffer[i];
+ }
+ return result;
+ }
+
+ @SuppressWarnings("nls")
+ @Override
+ public String toString() {
+ String slice = new String(this.buffer, this.start, this.length);
+ return "'" + slice + "'@(" + this.start + "," + this.length + ")";
+ }
+
+
+ @Override
+ public int compareTo(Key other) {
+ char[] b1 = this.buffer, b2 = other.buffer;
+
+ for(int i = this.start, j = other.start; i < b1.length && j < b2.length; i++, j++) {
+ if(b1[i] != b2[j])
+ return b1[i] < b2[j] ? -1 : 1;
+ }
+ return b1.length - b2.length;
+ }
+ }
+
+
+ /**
+ * Used to enforce preconditions.
+ * Note that the NPE thrown by mutator methods is thrown from the Key constructor.
+ *
+ * @throws IndexOutOfBoundsException if boundaries are wrong in any way
+ */
+ private static void checkBoundaries(char[] chars, int start, int length) {
+ if(start < 0 || length < 0 || start >= chars.length || start + length > chars.length)
+ throw new IndexOutOfBoundsException("Buffer length: " + chars.length + //$NON-NLS-1$
+ ", Start index: " + start + //$NON-NLS-1$
+ ", Length: " + length); //$NON-NLS-1$
+ }
+
+
+ private final Map<Key,V> map;
+
+
+ /**
+ * Constructs an empty CharArrayMap with default initial capacity.
+ */
+ public CharArrayMap() {
+ this.map = new HashMap<Key,V>();
+ }
+
+
+ /**
+ * Static factory method that constructs an empty CharArrayMap with default initial capacity,
+ * and the map will be kept in ascending key order.
+ *
+ * Characters are compared using a strictly numerical comparison; it is not locale-dependent.
+ */
+ public static <V> CharArrayMap<V> createOrderedMap() {
+ // TreeMap does not have a constructor that takes an initial capacity
+ return new CharArrayMap<V>(new TreeMap<Key, V>());
+ }
+
+
+ private CharArrayMap(Map<Key, V> map) {
+ assert map != null;
+ this.map = map;
+ }
+
+
+ /**
+ * Constructs an empty CharArrayMap with the given initial capacity.
+ * @throws IllegalArgumentException if the initial capacity is negative
+ */
+ public CharArrayMap(int initialCapacity) {
+ this.map = new HashMap<Key,V>(initialCapacity);
+ }
+
+ /**
+ * Creates a new mapping in this map, uses the given array slice as the key.
+ * If the map previously contained a mapping for this key, the old value is replaced.
+ * @throws NullPointerException if chars is null
+ * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+ */
+ public void put(char[] chars, int start, int length, V value) {
+ checkBoundaries(chars, start, length);
+ this.map.put(new Key(chars, start, length), value);
+ }
+
+ /**
+ * Creates a new mapping in this map, uses all of the given array as the key.
+ * If the map previously contained a mapping for this key, the old value is replaced.
+ * @throws NullPointerException if chars is null
+ */
+ public void put(char[] chars, V value) {
+ this.map.put(new Key(chars), value);
+ }
+
+ /**
+ * Returns the value to which the specified array slice is mapped in this map,
+ * or null if the map contains no mapping for this key.
+ * @throws NullPointerException if chars is null
+ * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+ */
+ public V get(char[] chars, int start, int length) {
+ checkBoundaries(chars, start, length);
+ return this.map.get(new Key(chars, start, length));
+ }
+
+ /**
+ * Returns the value to which the specified array is mapped in this map,
+ * or null if the map contains no mapping for this key.
+ * @throws NullPointerException if chars is null
+ */
+ public V get(char[] chars) {
+ return this.map.get(new Key(chars));
+ }
+
+ /**
+ * Removes the mapping for the given array slice if present.
+ * Returns the value object that corresponded to the key
+ * or null if the key was not in the map.
+ * @throws NullPointerException if chars is null
+ * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+ */
+ public V remove(char[] chars, int start, int length) {
+ checkBoundaries(chars, start, length);
+ return this.map.remove(new Key(chars, start, length));
+ }
+
+ /**
+ * Removes the mapping for the given array if present.
+ * Returns the value object that corresponded to the key
+ * or null if the key was not in the map.
+ * @throws NullPointerException if chars is null
+ */
+ public V remove(char[] chars) {
+ return this.map.remove(new Key(chars));
+ }
+
+ /**
+ * Returns true if the given key has a value associated with it in the map.
+ * @throws NullPointerException if chars is null
+ * @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
+ */
+ public boolean containsKey(char[] chars, int start, int length) {
+ checkBoundaries(chars, start, length);
+ return this.map.containsKey(new Key(chars, start, length));
+ }
+
+ /**
+ * Returns true if the given key has a value associated with it in the map.
+ * @throws NullPointerException if chars is null
+ */
+ public boolean containsKey(char[] chars) {
+ return this.map.containsKey(new Key(chars));
+ }
+
+ /**
+ * Returns true if the given value is contained in the map.
+ */
+ public boolean containsValue(V value) {
+ return this.map.containsValue(value);
+ }
+
+ /**
+ * Use this in a foreach loop.
+ */
+ public Collection<V> values() {
+ return this.map.values();
+ }
+
+ /**
+ * Returns the keys stored in the map.
+ */
+ public Collection<char[]> keys() {
+ Set<Key> keys= this.map.keySet();
+ ArrayList<char[]> r= new ArrayList<char[]>(keys.size());
+ for (Key key : keys) {
+ r.add(CharArrayUtils.extract(key.buffer, key.start, key.length));
+ }
+ return r;
+ }
+
+ /**
+ * Removes all mappings from the map.
+ */
+ public void clear() {
+ this.map.clear();
+ }
+
+ /**
+ * Returns the number of mappings.
+ */
+ public int size() {
+ return this.map.size();
+ }
+
+ /**
+ * Returns true if the map is empty.
+ */
+ public boolean isEmpty() {
+ return this.map.isEmpty();
+ }
+
+
+ /**
+ * Returns a String representation of the map.
+ */
+ @Override
+ public String toString() {
+ return this.map.toString();
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayUtils.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayUtils.java
new file mode 100644
index 000000000..1a5791eef
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/CharArrayUtils.java
@@ -0,0 +1,522 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2016 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Andrew Ferguson (Symbian)
+ * Markus Schorn (Wind River Systems)
+ * Sergey Prigogin (Google)
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.util;
+
+import java.util.Arrays;
+
+/**
+ * A static utility class for char arrays.
+ */
+public class CharArrayUtils {
+ /** @since 5.4 */
+ public static final char[] EMPTY_CHAR_ARRAY = {};
+ public static final char[] EMPTY = EMPTY_CHAR_ARRAY;
+ /** @since 5.7 */
+ public static final char[][] EMPTY_ARRAY_OF_CHAR_ARRAYS = {};
+
+ private CharArrayUtils() {}
+
+ public static final int hash(char[] str, int start, int length) {
+ int h = 0;
+ int end = start + length;
+
+ for (int curr = start; curr < end; ++curr) {
+ h = 31 * h + str[curr];
+ }
+
+ return h;
+ }
+
+ public static final int hash(char[] str) {
+ return hash(str, 0, str.length);
+ }
+
+ public static final boolean equals(char[] str1, char[] str2) {
+ return Arrays.equals(str1, str2);
+ }
+
+ public static final boolean equals(char[][] strarr1, char[][] strarr2) {
+ if (strarr1 == strarr2) {
+ return true;
+ }
+ if (strarr1 == null || strarr2 == null) {
+ return false;
+ }
+ if (strarr1.length != strarr2.length) {
+ return false;
+ }
+ for (int i = 0; i < strarr2.length; i++) {
+ if (!Arrays.equals(strarr1[i], strarr2[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns {@code true} if the contents of a character array are the same as contents
+ * of a string.
+ * @since 5.4
+ */
+ public static final boolean equals(char[] str1, String str2) {
+ int length = str1.length;
+ if (str2.length() != length)
+ return false;
+
+ for (int i = 0; i < length; i++) {
+ if (str1[i] != str2.charAt(i))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns true iff the given array contains the given char at the given position
+ */
+ public static final boolean hasCharAt(char toLookFor, int position, char[] toSearch) {
+ if (toSearch.length <= position) {
+ return false;
+ }
+
+ return toSearch[position] == toLookFor;
+ }
+
+ /**
+ * Returns {@code true} if the contents of a section of a character array are the same as contents of a string.
+ *
+ * @since 5.5
+ */
+ public static final boolean equals(char[] str1, int start1, int length1, String str2) {
+ if (length1 != str2.length() || str1.length < length1 + start1)
+ return false;
+ for (int i = 0; i < length1; ++i) {
+ if (str1[start1++] != str2.charAt(i))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns {@code true} if a prefix of the character array is the same as contents
+ * of a string.
+ * @since 5.4
+ */
+ public static final boolean startsWith(char[] str1, String str2) {
+ int len = str2.length();
+ if (str1.length < len)
+ return false;
+ for (int i = 0; i < len; i++) {
+ if (str1[i] != str2.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Implements a lexicographical comparator for char arrays. Comparison is done
+ * on a per char basis, not a code-point basis.
+ *
+ * @param str1 the first of the two char arrays to compare
+ * @param str2 the second of the two char arrays to compare
+ * @return 0 if str1==str2, -1 if str1 &lt; str2 and 1 if str1 &gt; str2
+ */
+ /*
+ * aftodo - we should think about using the Character codepoint static methods
+ * if we move to Java 5
+ */
+ public static final int compare(char[] str1, char[] str2) {
+ if (str1 == str2)
+ return 0;
+
+ int end= Math.min(str1.length, str2.length);
+ for (int i = 0; i < end; ++i) {
+ int diff= str1[i] - str2[i];
+ if (diff != 0)
+ return diff;
+ }
+
+ return str1.length - str2.length;
+ }
+
+ /**
+ * Returns {@code true} if the contents of a section of a character array are the same as
+ * contents of another character array.
+ */
+ public static final boolean equals(char[] str1, int start1, int length1, char[] str2) {
+ if (length1 != str2.length || str1.length < length1 + start1)
+ return false;
+ if (str1 == str2 && start1 == 0)
+ return true;
+ for (int i = 0; i < length1; ++i) {
+ if (str1[start1++] != str2[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ public static final boolean equals(char[] str1, int start1, int length1, char[] str2, boolean ignoreCase) {
+ if (!ignoreCase)
+ return equals(str1, start1, length1, str2);
+
+ if (length1 != str2.length || str1.length < start1 + length1)
+ return false;
+
+ for (int i = 0; i < length1; ++i) {
+ if (Character.toLowerCase(str1[start1++]) != Character.toLowerCase(str2[i]))
+ return false;
+ }
+ return true;
+ }
+
+ public static final char[] extract(char[] str, int start, int length) {
+ if (start == 0 && length == str.length)
+ return str;
+
+ char[] copy = new char[length];
+ System.arraycopy(str, start, copy, 0, length);
+ return copy;
+ }
+
+ public static final char[] concat(char[] first, char[] second) {
+ if (first == null)
+ return second;
+ if (second == null)
+ return first;
+
+ int length1 = first.length;
+ int length2 = second.length;
+ char[] result = new char[length1 + length2];
+ System.arraycopy(first, 0, result, 0, length1);
+ System.arraycopy(second, 0, result, length1, length2);
+ return result;
+ }
+
+ public static final char[] concat(char[] first, char[] second, char[] third) {
+ if (first == null)
+ return concat(second, third);
+ if (second == null)
+ return concat(first, third);
+ if (third == null)
+ return concat(first, second);
+
+ int length1 = first.length;
+ int length2 = second.length;
+ int length3 = third.length;
+ char[] result = new char[length1 + length2 + length3];
+ System.arraycopy(first, 0, result, 0, length1);
+ System.arraycopy(second, 0, result, length1, length2);
+ System.arraycopy(third, 0, result, length1 + length2, length3);
+ return result;
+ }
+
+ public static final char[] concat(char[] first, char[] second, char[] third, char[] fourth) {
+ if (first == null)
+ return concat(second, third, fourth);
+ if (second == null)
+ return concat(first, third, fourth);
+ if (third == null)
+ return concat(first, second, fourth);
+ if (fourth == null)
+ return concat(first, second, third);
+
+ int length1 = first.length;
+ int length2 = second.length;
+ int length3 = third.length;
+ int length4 = fourth.length;
+ char[] result = new char[length1 + length2 + length3 + length4];
+ System.arraycopy(first, 0, result, 0, length1);
+ System.arraycopy(second, 0, result, length1, length2);
+ System.arraycopy(third, 0, result, length1 + length2, length3);
+ System.arraycopy(fourth, 0, result, length1 + length2 + length3, length4);
+ return result;
+ }
+
+ /**
+ * Answers a new array which is the concatenation of all the given arrays.
+ *
+ * @param toCatenate
+ * @since 3.12
+ */
+ public static char[] concat(char[]... toCatenate) {
+ int totalSize = 0;
+ for (char[] next: toCatenate) {
+ totalSize += next.length;
+ }
+
+ char[] result = new char[totalSize];
+ int writeIndex = 0;
+ for (char[] next: toCatenate) {
+ if (next == null) {
+ continue;
+ }
+ System.arraycopy(next, 0, result, writeIndex, next.length);
+ writeIndex += next.length;
+ }
+ return result;
+ }
+
+ public static final char[] replace(char[] array, char[] toBeReplaced, char[] replacementChars) {
+ int max = array.length;
+ int replacedLength = toBeReplaced.length;
+ int replacementLength = replacementChars.length;
+
+ int[] starts = new int[5];
+ int occurrenceCount = 0;
+
+ if (!equals(toBeReplaced, replacementChars)) {
+ next: for (int i = 0; i < max; i++) {
+ int j = 0;
+ while (j < replacedLength) {
+ if (i + j == max)
+ continue next;
+ if (array[i + j] != toBeReplaced[j++])
+ continue next;
+ }
+ if (occurrenceCount == starts.length) {
+ System.arraycopy(starts, 0, starts = new int[occurrenceCount * 2], 0,
+ occurrenceCount);
+ }
+ starts[occurrenceCount++] = i;
+ }
+ }
+ if (occurrenceCount == 0)
+ return array;
+ char[] result = new char[max + occurrenceCount * (replacementLength - replacedLength)];
+ int inStart = 0, outStart = 0;
+ for (int i = 0; i < occurrenceCount; i++) {
+ int offset = starts[i] - inStart;
+ System.arraycopy(array, inStart, result, outStart, offset);
+ inStart += offset;
+ outStart += offset;
+ System.arraycopy(
+ replacementChars,
+ 0,
+ result,
+ outStart,
+ replacementLength);
+ inStart += replacedLength;
+ outStart += replacementLength;
+ }
+ System.arraycopy(array, inStart, result, outStart, max - inStart);
+ return result;
+ }
+
+ public static final char[][] subarray(char[][] array, int start, int end) {
+ if (end == -1)
+ end = array.length;
+ if (start > end)
+ return null;
+ if (start < 0)
+ return null;
+ if (end > array.length)
+ return null;
+
+ char[][] result = new char[end - start][];
+ System.arraycopy(array, start, result, 0, end - start);
+ return result;
+ }
+
+ public static final char[] subarray(char[] array, int start, int end) {
+ if (end == -1)
+ end = array.length;
+ if (start > end)
+ return null;
+ if (start < 0)
+ return null;
+ if (end > array.length)
+ return null;
+
+ char[] result = new char[end - start];
+ System.arraycopy(array, start, result, 0, end - start);
+ return result;
+ }
+
+ public static final int indexOf(char toBeFound, char[] array) {
+ for (int i = 0; i < array.length; i++) {
+ if (toBeFound == array[i])
+ return i;
+ }
+ return -1;
+ }
+
+ public static int indexOf(char toBeFound, char[] buffer, int start, int end) {
+ if (start < 0 || start > buffer.length || end > buffer.length)
+ return -1;
+
+ for (int i = start; i < end; i++) {
+ if (toBeFound == buffer[i])
+ return i;
+ }
+ return -1;
+ }
+
+ public static final int indexOf(char[] toBeFound, char[] array) {
+ if (toBeFound.length > array.length)
+ return -1;
+
+ int j = 0;
+ for (int i = 0; i < array.length; i++) {
+ if (toBeFound[j] == array[i]) {
+ if (++j == toBeFound.length)
+ return i - j + 1;
+ } else {
+ j = 0;
+ }
+ }
+ return -1;
+ }
+
+ public static final int lastIndexOf(char[] toBeFound, char[] array) {
+ return lastIndexOf(toBeFound, array, 0);
+ }
+
+ /**
+ * @since 5.11
+ */
+ public static int lastIndexOf(char toBeFound, char[] array) {
+ return lastIndexOf(toBeFound, array, 0);
+ }
+
+ /**
+ * @since 5.11
+ */
+ public static int lastIndexOf(char toBeFound, char[] array, int fromIndex) {
+ for (int i = array.length; --i >= fromIndex;) {
+ if (array[i] == toBeFound) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @since 5.11
+ */
+ public static int lastIndexOf(char[] toBeFound, char[] array, int fromIndex) {
+ int i = array.length;
+ int j = toBeFound.length;
+ while (true) {
+ if (--j < 0)
+ return i;
+ if (--i < fromIndex)
+ return -1;
+ if (toBeFound[j] != array[i]) {
+ i += toBeFound.length - j - 1;
+ j = toBeFound.length;
+ }
+ }
+ }
+
+ static final public char[] trim(char[] chars) {
+ if (chars == null)
+ return null;
+
+ int length = chars.length;
+ int start = 0;
+ while (start < length && chars[start] == ' ') {
+ start++;
+ }
+ if (start == length)
+ return EMPTY_CHAR_ARRAY;
+
+ int end = length;
+ while (--end > start && chars[end] == ' ') {
+ // Nothing to do
+ }
+ end++;
+ if (start == 0 && end == length)
+ return chars;
+ return subarray(chars, start, end);
+ }
+
+ static final public char[] lastSegment(char[] array, char[] separator) {
+ int pos = lastIndexOf(separator, array);
+ if (pos < 0)
+ return array;
+ return subarray(array, pos + separator.length, array.length);
+ }
+
+ /**
+ * @param buff
+ * @param i
+ * @param charImage
+ */
+ public static void overWrite(char[] buff, int i, char[] charImage) {
+ if (buff.length < i + charImage.length)
+ return;
+ for (int j = 0; j < charImage.length; j++) {
+ buff[i + j] = charImage[j];
+ }
+ }
+
+ /**
+ * Finds an array of chars in an array of arrays of chars.
+ *
+ * @return offset where the array was found or {@code -1}
+ */
+ public static int indexOf(final char[] searchFor, final char[][] searchIn) {
+ for (int i = 0; i < searchIn.length; i++) {
+ if (equals(searchIn[i], searchFor)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Converts a {@link StringBuilder} to a character array.
+ * @since 5.5
+ */
+ public static char[] extractChars(StringBuilder buf) {
+ final int len = buf.length();
+ if (len == 0)
+ return EMPTY_CHAR_ARRAY;
+ char[] result= new char[len];
+ buf.getChars(0, len, result, 0);
+ return result;
+ }
+
+ public static char[] subarray(char[] inputString, int index) {
+ if (inputString.length <= index) {
+ return EMPTY_CHAR_ARRAY;
+ }
+
+ char[] result = new char[inputString.length - index];
+ System.arraycopy(inputString, index, result, 0, result.length);
+ return result;
+ }
+
+ public static boolean startsWith(char[] fieldDescriptor, char c) {
+ return fieldDescriptor.length > 0 && fieldDescriptor[0] == c;
+ }
+
+ /**
+ * If the given array is null, returns the empty array. Otherwise, returns the argument.
+ */
+ public static char[] notNull(char[] contents) {
+ if (contents == null) {
+ return EMPTY_CHAR_ARRAY;
+ }
+ return contents;
+ }
+
+ public static boolean endsWith(char[] fieldDescriptor, char c) {
+ if (fieldDescriptor.length == 0) {
+ return false;
+ }
+ return fieldDescriptor[fieldDescriptor.length - 1] == c;
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/PathMap.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/PathMap.java
new file mode 100644
index 000000000..f328fe041
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/nd/util/PathMap.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.nd.util;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Maps IPath keys onto values.
+ */
+public class PathMap<T> {
+ private static class Node<T> {
+ int depth;
+ boolean exists;
+ T value;
+ Map<String, Node<T>> children;
+
+ Node(int depth) {
+ this.depth = depth;
+ }
+
+ String getSegment(IPath key) {
+ return key.segment(this.depth);
+ }
+
+ Node<T> createNode(IPath key) {
+ if (this.depth == key.segmentCount()) {
+ this.exists = true;
+ return this;
+ }
+
+ String nextSegment = getSegment(key);
+ Node<T> next = createChild(nextSegment);
+ return next.createNode(key);
+ }
+
+ public Node<T> createChild(String nextSegment) {
+ if (this.children == null) {
+ this.children = new HashMap<>();
+ }
+ Node<T> next = this.children.get(nextSegment);
+ if (next == null) {
+ next = new Node<>(this.depth + 1);
+ this.children.put(nextSegment, next);
+ }
+ return next;
+ }
+
+ public Node<T> getMostSpecificNode(IPath key) {
+ if (this.depth == key.segmentCount()) {
+ return this;
+ }
+ String nextSegment = getSegment(key);
+
+ Node<T> child = getChild(nextSegment);
+ if (child == null) {
+ return this;
+ }
+ Node<T> result = child.getMostSpecificNode(key);
+ if (result.exists) {
+ return result;
+ } else {
+ return this;
+ }
+ }
+
+ Node<T> getChild(String nextSegment) {
+ if (this.children == null) {
+ return null;
+ }
+ return this.children.get(nextSegment);
+ }
+
+ public void addAllKeys(Set<IPath> result, IPath parent) {
+ if (this.exists) {
+ result.add(parent);
+ }
+
+ if (this.children == null) {
+ return;
+ }
+
+ for (Entry<String, Node<T>> next : this.children.entrySet()) {
+ String key = next.getKey();
+ IPath nextPath = buildChildPath(parent, key);
+ next.getValue().addAllKeys(result, nextPath);
+ }
+ }
+
+ IPath buildChildPath(IPath parent, String key) {
+ IPath nextPath = parent.append(key);
+ return nextPath;
+ }
+
+ public void toString(StringBuilder builder, IPath parentPath) {
+ if (this.exists) {
+ builder.append("["); //$NON-NLS-1$
+ builder.append(parentPath);
+ builder.append("] = "); //$NON-NLS-1$
+ builder.append(this.value);
+ builder.append("\n"); //$NON-NLS-1$
+ }
+ if (this.children != null) {
+ for (Entry<String, Node<T>> next : this.children.entrySet()) {
+ String key = next.getKey();
+ IPath nextPath = buildChildPath(parentPath, key);
+ next.getValue().toString(builder, nextPath);
+ }
+ }
+ }
+ }
+
+ private static class DeviceNode<T> extends Node<T> {
+ Node<T> noDevice = new Node<>(0);
+
+ DeviceNode() {
+ super(-1);
+ }
+
+ @Override
+ String getSegment(IPath key) {
+ return key.getDevice();
+ }
+
+ @Override
+ public Node<T> createChild(String nextSegment) {
+ if (nextSegment == null) {
+ return this.noDevice;
+ }
+ return super.createChild(nextSegment);
+ }
+
+ Node<T> getChild(String nextSegment) {
+ if (nextSegment == null) {
+ return this.noDevice;
+ }
+ return super.getChild(nextSegment);
+ }
+
+ @Override
+ IPath buildChildPath(IPath parent, String key) {
+ IPath nextPath = Path.EMPTY.append(parent);
+ nextPath.setDevice(key);
+ return nextPath;
+ }
+
+ @Override
+ public void toString(StringBuilder builder, IPath parentPath) {
+ this.noDevice.toString(builder, parentPath);
+ super.toString(builder,parentPath);
+ }
+ }
+
+ private Node<T> root = new DeviceNode<T>();
+
+ /**
+ * Inserts the given key into the map.
+ */
+ public T put(IPath key, T value) {
+ Node<T> node = this.root.createNode(key);
+ T result = node.value;
+ node.value = value;
+ return result;
+ }
+
+ /**
+ * Returns the value associated with the given key
+ */
+ public T get(IPath key) {
+ Node<T> node = this.root.getMostSpecificNode(key);
+ if (!node.exists || node.depth < key.segmentCount()) {
+ return null;
+ }
+ return node.value;
+ }
+
+ /**
+ * Returns the value associated with the longest prefix of the given key
+ * that can be found in the map.
+ */
+ public T getMostSpecific(IPath key) {
+ Node<T> node = this.root.getMostSpecificNode(key);
+ if (!node.exists) {
+ return null;
+ }
+ return node.value;
+ }
+
+ /**
+ * Returns true iff any key in this map is a prefix of the given path.
+ */
+ public boolean containsPrefixOf(IPath path) {
+ Node<T> node = this.root.getMostSpecificNode(path);
+ return node.exists;
+ }
+
+ public Set<IPath> keySet() {
+ Set<IPath> result = new HashSet<>();
+
+ this.root.addAllKeys(result, Path.EMPTY);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+
+ this.root.toString(builder, Path.EMPTY);
+ return builder.toString();
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/UnindexedSearchScope.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/UnindexedSearchScope.java
new file mode 100644
index 000000000..15ab020d3
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/UnindexedSearchScope.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.search;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaElementDelta;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+
+public class UnindexedSearchScope extends AbstractSearchScope {
+ private IJavaSearchScope searchScope;
+
+ private UnindexedSearchScope(IJavaSearchScope scope) {
+ this.searchScope = scope;
+ }
+
+ public static IJavaSearchScope filterEntriesCoveredByTheNewIndex(IJavaSearchScope scope) {
+ return new UnindexedSearchScope(scope);
+ }
+
+ @Override
+ public boolean encloses(String resourcePathString) {
+ int separatorIndex = resourcePathString.indexOf(JAR_FILE_ENTRY_SEPARATOR);
+ if (separatorIndex != -1) {
+ // Files within jar files would have been indexed
+ return false;
+ }
+
+ // Jar files themselves would have been indexed
+ if (isJarFile(resourcePathString)) {
+ return false;
+ }
+
+ // Consult with the search scope
+ return this.searchScope.encloses(resourcePathString);
+ }
+
+ private boolean isJarFile(String possibleJarFile) {
+ if (possibleJarFile == null) {
+ return false;
+ }
+ return (possibleJarFile.endsWith(".jar") || possibleJarFile.endsWith(".JAR")); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ @Override
+ public boolean encloses(IJavaElement element) {
+ try {
+ IResource underlyingResource = element.getUnderlyingResource();
+
+ if (underlyingResource != null && isJarFile(underlyingResource.getName())) {
+ return false;
+ }
+ } catch (JavaModelException e) {
+ JavaCore.getPlugin().getLog().log(e.getStatus());
+ }
+ return this.searchScope.encloses(element);
+ }
+
+ @Override
+ public IPath[] enclosingProjectsAndJars() {
+ IPath[] unfiltered = this.searchScope.enclosingProjectsAndJars();
+
+ List<IPath> result = new ArrayList<>();
+
+ for (IPath next : unfiltered) {
+ if (isJarFile(next.lastSegment())) {
+ continue;
+ }
+ result.add(next);
+ }
+ return result.toArray(new IPath[result.size()]);
+ }
+
+ @Override
+ public void processDelta(IJavaElementDelta delta, int eventType) {
+ if (this.searchScope instanceof AbstractSearchScope) {
+ AbstractSearchScope inner = (AbstractSearchScope) this.searchScope;
+
+ inner.processDelta(delta, eventType);
+ }
+ }
+
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java
index 1f53509ec..7ce53faf5 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/IndexManager.java
@@ -10,27 +10,48 @@
*******************************************************************************/
package org.eclipse.jdt.internal.core.search.indexing;
-import java.io.*;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Map;
import java.util.zip.CRC32;
-import org.eclipse.core.resources.*;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
-import org.eclipse.jdt.core.*;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.core.search.*;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchDocument;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.internal.compiler.ISourceElementRequestor;
import org.eclipse.jdt.internal.compiler.SourceElementParser;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
import org.eclipse.jdt.internal.compiler.util.SimpleSet;
-import org.eclipse.jdt.internal.core.*;
-import org.eclipse.jdt.internal.core.index.*;
+import org.eclipse.jdt.internal.core.ClasspathEntry;
+import org.eclipse.jdt.internal.core.JavaModel;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.index.DiskIndex;
+import org.eclipse.jdt.internal.core.index.FileIndexLocation;
+import org.eclipse.jdt.internal.core.index.Index;
+import org.eclipse.jdt.internal.core.index.IndexLocation;
+import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
import org.eclipse.jdt.internal.core.search.PatternSearchJob;
import org.eclipse.jdt.internal.core.search.processing.IJob;
@@ -77,6 +98,8 @@ public class IndexManager extends JobManager implements IIndexConstants {
public synchronized void aboutToUpdateIndex(IPath containerPath, Integer newIndexState) {
+ // TODO(sxenos): Find a more appropriate and more specific place to trigger re-indexing
+ Indexer.getInstance().rescanAll();
// newIndexState is either UPDATING_STATE or REBUILDING_STATE
// must tag the index as inconsistent, in case we exit before the update job is started
IndexLocation indexLocation = computeIndexLocation(containerPath);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java
index 58e3f0eab..451231e59 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -10,10 +10,13 @@
*******************************************************************************/
package org.eclipse.jdt.internal.core.search.indexing;
+import java.util.Collections;
+
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.CharOperation;
@@ -49,7 +52,7 @@ import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.SourceTypeElementInfo;
import org.eclipse.jdt.internal.core.jdom.CompilationUnit;
-import org.eclipse.jdt.internal.core.search.matching.JavaSearchNameEnvironment;
+import org.eclipse.jdt.internal.core.search.matching.IndexBasedJavaSearchEnvironment;
import org.eclipse.jdt.internal.core.search.matching.MethodPattern;
import org.eclipse.jdt.internal.core.search.processing.JobManager;
import org.eclipse.objectteams.otdt.internal.core.compiler.control.Dependencies;
@@ -165,7 +168,7 @@ public class SourceIndexer extends AbstractIndexer implements ITypeRequestor, Su
this.cud = this.basicParser.parse(this.compilationUnit, new CompilationResult(this.compilationUnit, 0, 0, this.options.maxProblemsPerUnit));
// Use a non model name environment to avoid locks, monitors and such.
- INameEnvironment nameEnvironment = new JavaSearchNameEnvironment(javaProject, JavaModelManager.getJavaModelManager().getWorkingCopies(DefaultWorkingCopyOwner.PRIMARY, true/*add primary WCs*/));
+ INameEnvironment nameEnvironment = IndexBasedJavaSearchEnvironment.create(Collections.singletonList((IJavaProject)javaProject), JavaModelManager.getJavaModelManager().getWorkingCopies(DefaultWorkingCopyOwner.PRIMARY, true/*add primary WCs*/));
this.lookupEnvironment = new LookupEnvironment(this, this.options, problemReporter, nameEnvironment);
reduceParseTree(this.cud);
//{ObjectTeams: need Dependencies configured:
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java
index 6e8e133ca..48e281952 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/ClassFileMatchLocator.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -18,10 +18,9 @@ import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.*;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
-import org.eclipse.jdt.internal.compiler.classfmt.FieldInfo;
-import org.eclipse.jdt.internal.compiler.classfmt.MethodInfo;
import org.eclipse.jdt.internal.compiler.env.*;
import org.eclipse.jdt.internal.compiler.lookup.*;
+import org.eclipse.jdt.internal.core.BinaryType;
import org.eclipse.jdt.internal.core.*;
import org.eclipse.jdt.internal.core.search.indexing.IIndexConstants;
@@ -352,10 +351,10 @@ private void matchAnnotations(SearchPattern pattern, MatchLocator locator, Class
}
// Look for references in methods annotations
- MethodInfo[] methods = (MethodInfo[]) binaryType.getMethods();
+ IBinaryMethod[] methods = binaryType.getMethods();
if (methods != null) {
for (int i = 0, max = methods.length; i < max; i++) {
- MethodInfo method = methods[i];
+ IBinaryMethod method = methods[i];
if (checkAnnotations(typeReferencePattern, method.getAnnotations(), method.getTagBits())) {
binaryTypeBinding = locator.cacheBinaryType(classFileBinaryType, binaryType);
IMethod methodHandle = classFileBinaryType.getMethod(
@@ -370,10 +369,10 @@ private void matchAnnotations(SearchPattern pattern, MatchLocator locator, Class
}
// Look for references in fields annotations
- FieldInfo[] fields = (FieldInfo[]) binaryType.getFields();
+ IBinaryField[] fields = binaryType.getFields();
if (fields != null) {
for (int i = 0, max = fields.length; i < max; i++) {
- FieldInfo field = fields[i];
+ IBinaryField field = fields[i];
if (checkAnnotations(typeReferencePattern, field.getAnnotations(), field.getTagBits())) {
IField fieldHandle = classFileBinaryType.getField(new String(field.getName()));
TypeReferenceMatch match = new TypeReferenceMatch(fieldHandle, SearchMatch.A_ACCURATE, -1, 0, false, locator.getParticipant(), locator.currentPossibleMatch.resource);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/IndexBasedJavaSearchEnvironment.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/IndexBasedJavaSearchEnvironment.java
new file mode 100644
index 000000000..907707501
--- /dev/null
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/IndexBasedJavaSearchEnvironment.java
@@ -0,0 +1,333 @@
+/*******************************************************************************
+ * Copyright (c) 2015, 2016 Google, Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Xenos (Google) - Initial implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.core.search.matching;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
+import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
+import org.eclipse.jdt.internal.core.ClasspathEntry;
+import org.eclipse.jdt.internal.core.JavaModel;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.PackageFragmentRoot;
+import org.eclipse.jdt.internal.core.builder.ClasspathLocation;
+import org.eclipse.jdt.internal.core.nd.IReader;
+import org.eclipse.jdt.internal.core.nd.Nd;
+import org.eclipse.jdt.internal.core.nd.field.FieldSearchIndex;
+import org.eclipse.jdt.internal.core.nd.java.JavaIndex;
+import org.eclipse.jdt.internal.core.nd.java.JavaNames;
+import org.eclipse.jdt.internal.core.nd.java.NdResourceFile;
+import org.eclipse.jdt.internal.core.nd.java.NdType;
+import org.eclipse.jdt.internal.core.nd.java.NdTypeId;
+import org.eclipse.jdt.internal.core.nd.java.TypeRef;
+import org.eclipse.jdt.internal.core.nd.java.model.IndexBinaryType;
+import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
+import org.eclipse.jdt.internal.core.nd.util.PathMap;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class IndexBasedJavaSearchEnvironment implements INameEnvironment, SuffixConstants {
+
+ private Map<String, ICompilationUnit> workingCopies;
+ private PathMap<Integer> mapPathsToRoots = new PathMap<>();
+ private IPackageFragmentRoot[] roots;
+ private int sourceEntryPosition;
+ private List<ClasspathLocation> unindexedEntries = new ArrayList<>();
+
+ public IndexBasedJavaSearchEnvironment(List<IJavaProject> javaProject, org.eclipse.jdt.core.ICompilationUnit[] copies) {
+ this.workingCopies = JavaSearchNameEnvironment.getWorkingCopyMap(copies);
+
+ try {
+ List<IPackageFragmentRoot> localRoots = new ArrayList<>();
+
+ for (IJavaProject next : javaProject) {
+ for (IPackageFragmentRoot nextRoot : next.getAllPackageFragmentRoots()) {
+ IPath path = nextRoot.getPath();
+ if (!nextRoot.isArchive()) {
+ Object target = JavaModel.getTarget(path, true);
+ if (target != null) {
+ ClasspathLocation cp;
+ if (nextRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
+ PackageFragmentRoot root = (PackageFragmentRoot)nextRoot;
+ cp = new ClasspathSourceDirectory((IContainer)target, root.fullExclusionPatternChars(), root.fullInclusionPatternChars());
+ this.unindexedEntries.add(cp);
+ }
+ }
+ }
+
+ localRoots.add(nextRoot);
+ }
+ }
+
+ this.roots = localRoots.toArray(new IPackageFragmentRoot[0]);
+ } catch (JavaModelException e) {
+ this.roots = new IPackageFragmentRoot[0];
+ // project doesn't exist
+ }
+
+ // Build the map of paths onto root indices
+ int length = this.roots.length;
+ for (int i = 0; i < length; i++) {
+ IPath nextPath = JavaIndex.getLocationForElement(this.roots[i]);
+ this.mapPathsToRoots.put(nextPath, i);
+ }
+
+ // Locate the position of the first source entry
+ this.sourceEntryPosition = Integer.MAX_VALUE;
+ for (int i = 0; i < length; i++) {
+ IPackageFragmentRoot nextRoot = this.roots[i];
+ try {
+ if (nextRoot.getKind() == IPackageFragmentRoot.K_SOURCE) {
+ this.sourceEntryPosition = i;
+ break;
+ }
+ } catch (JavaModelException e) {
+ // project doesn't exist
+ }
+ }
+ }
+
+ public static boolean isEnabled() {
+ return Platform.getPreferencesService().getBoolean(JavaCore.PLUGIN_ID, "useIndexBasedSearchEnvironment", false, //$NON-NLS-1$
+ null);
+ }
+
+ @Override
+ public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
+ char[] binaryName = CharOperation.concatWith(compoundTypeName, '/');
+
+ int bestEntryPosition = Integer.MAX_VALUE;
+ NameEnvironmentAnswer result = findClassInUnindexedLocations(new String(binaryName), compoundTypeName[compoundTypeName.length - 1]);
+ if (result != null) {
+ bestEntryPosition = this.sourceEntryPosition;
+ }
+
+ char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(binaryName);
+ JavaIndex index = JavaIndex.getIndex();
+ Nd nd = index.getNd();
+ try (IReader lock = nd.acquireReadLock()) {
+ NdTypeId typeId = index.findType(fieldDescriptor);
+
+ if (typeId != null) {
+ List<NdType> types = typeId.getTypes();
+ for (NdType next : types) {
+ NdResourceFile resource = next.getFile();
+
+ IPath path = resource.getPath();
+ Integer nextRoot = this.mapPathsToRoots.getMostSpecific(path);
+ if (nextRoot != null) {
+ IPackageFragmentRoot root = this.roots[nextRoot];
+
+ ClasspathEntry classpathEntry = (ClasspathEntry)root.getRawClasspathEntry();
+ AccessRuleSet ruleSet = classpathEntry.getAccessRuleSet();
+ AccessRestriction accessRestriction = ruleSet == null? null : ruleSet.getViolatedRestriction(binaryName);
+ TypeRef typeRef = TypeRef.create(next);
+ String fileName = new String(binaryName) + ".class"; //$NON-NLS-1$
+ IBinaryType binaryType = new IndexBinaryType(typeRef, fileName.toCharArray());
+ NameEnvironmentAnswer nextAnswer = new NameEnvironmentAnswer(binaryType, accessRestriction);
+
+ boolean useNewAnswer = isBetter(result, bestEntryPosition, nextAnswer, nextRoot);
+
+ if (useNewAnswer) {
+ bestEntryPosition = nextRoot;
+ result = nextAnswer;
+ }
+ }
+ }
+ }
+ } catch (JavaModelException e) {
+ // project doesn't exist
+ }
+
+ return result;
+ }
+
+ /**
+ * Search unindexed locations on the classpath for the given class
+ */
+ private NameEnvironmentAnswer findClassInUnindexedLocations(String qualifiedTypeName, char[] typeName) {
+ String
+ binaryFileName = null, qBinaryFileName = null,
+ sourceFileName = null, qSourceFileName = null,
+ qPackageName = null;
+ NameEnvironmentAnswer suggestedAnswer = null;
+ Iterator <ClasspathLocation> iter = this.unindexedEntries.iterator();
+ while (iter.hasNext()) {
+ ClasspathLocation location = iter.next();
+ NameEnvironmentAnswer answer;
+ if (location instanceof ClasspathSourceDirectory) {
+ if (sourceFileName == null) {
+ qSourceFileName = qualifiedTypeName; // doesn't include the file extension
+ sourceFileName = qSourceFileName;
+ qPackageName = ""; //$NON-NLS-1$
+ if (qualifiedTypeName.length() > typeName.length) {
+ int typeNameStart = qSourceFileName.length() - typeName.length;
+ qPackageName = qSourceFileName.substring(0, typeNameStart - 1);
+ sourceFileName = qSourceFileName.substring(typeNameStart);
+ }
+ }
+ org.eclipse.jdt.internal.compiler.env.ICompilationUnit workingCopy = (org.eclipse.jdt.internal.compiler.env.ICompilationUnit) this.workingCopies.get(qualifiedTypeName);
+ if (workingCopy != null) {
+ answer = new NameEnvironmentAnswer(workingCopy, null /*no access restriction*/);
+ } else {
+ answer = location.findClass(
+ sourceFileName, // doesn't include the file extension
+ qPackageName,
+ qSourceFileName); // doesn't include the file extension
+ }
+ } else {
+ if (binaryFileName == null) {
+ qBinaryFileName = qualifiedTypeName + SUFFIX_STRING_class;
+ binaryFileName = qBinaryFileName;
+ qPackageName = ""; //$NON-NLS-1$
+ if (qualifiedTypeName.length() > typeName.length) {
+ int typeNameStart = qBinaryFileName.length() - typeName.length - 6; // size of ".class"
+ qPackageName = qBinaryFileName.substring(0, typeNameStart - 1);
+ binaryFileName = qBinaryFileName.substring(typeNameStart);
+ }
+ }
+ answer =
+ location.findClass(
+ binaryFileName,
+ qPackageName,
+ qBinaryFileName);
+ }
+ if (answer != null) {
+ if (!answer.ignoreIfBetter()) {
+ if (answer.isBetter(suggestedAnswer))
+ return answer;
+ } else if (answer.isBetter(suggestedAnswer))
+ // remember suggestion and keep looking
+ suggestedAnswer = answer;
+ }
+ }
+ if (suggestedAnswer != null)
+ // no better answer was found
+ return suggestedAnswer;
+ return null;
+ }
+
+ public boolean isBetter(NameEnvironmentAnswer currentBest, int currentBestClasspathPosition,
+ NameEnvironmentAnswer toTest, int toTestClasspathPosition) {
+ boolean useNewAnswer = false;
+
+ if (currentBest == null) {
+ useNewAnswer = true;
+ } else {
+ if (toTest.isBetter(currentBest)) {
+ useNewAnswer = true;
+ } else {
+ // If neither one is better, use the one with the earlier classpath position
+ if (!currentBest.isBetter(toTest)) {
+ useNewAnswer = (toTestClasspathPosition < currentBestClasspathPosition);
+ }
+ }
+ }
+ return useNewAnswer;
+ }
+
+ @Override
+ public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) {
+ char[][] newArray = new char[packageName.length + 1][];
+ for (int idx = 0; idx < packageName.length; idx++) {
+ newArray[idx] = packageName[idx];
+ }
+ newArray[packageName.length] = typeName;
+ return findType(newArray);
+ }
+
+ @Override
+ public boolean isPackage(char[][] parentPackageName, char[] packageName) {
+ char[] binaryPackageName = CharOperation.concatWith(parentPackageName, '/');
+ final char[] fieldDescriptorPrefix;
+
+ if (parentPackageName == null || parentPackageName.length == 0) {
+ fieldDescriptorPrefix = CharArrayUtils.concat(JavaNames.FIELD_DESCRIPTOR_PREFIX, packageName,
+ new char[] { '/' });
+ } else {
+ fieldDescriptorPrefix = CharArrayUtils.concat(JavaNames.FIELD_DESCRIPTOR_PREFIX, binaryPackageName,
+ new char[] { '/' }, packageName, new char[] { '/' });
+ }
+
+ // Search all the types that are a subpackage of the given package name. Return if we find any one of them on
+ // the classpath of this project.
+ JavaIndex index = JavaIndex.getIndex();
+ Nd nd = index.getNd();
+ try (IReader lock = nd.acquireReadLock()) {
+ return !index.visitFieldDescriptorsStartingWith(fieldDescriptorPrefix,
+ new FieldSearchIndex.Visitor<NdTypeId>() {
+ @Override
+ public boolean visit(NdTypeId typeId) {
+ //String fd = typeId.getFieldDescriptor().getString();
+ // If this is an exact match for the field descriptor prefix we're looking for then
+ // this class can't be part of the package we're searching for (and, most likely, the
+ // "package" we're searching for is actually a class name - not a package).
+ if (typeId.getFieldDescriptor().length() <= fieldDescriptorPrefix.length + 1) {
+ return true;
+ }
+ List<NdType> types = typeId.getTypes();
+ for (NdType next : types) {
+ if (next.isMember() || next.isLocal() || next.isAnonymous()) {
+ continue;
+ }
+ NdResourceFile resource = next.getFile();
+
+ IPath path = resource.getPath();
+
+ if (containsPrefixOf(path)) {
+ // Terminate the search -- we've found a class belonging to the package
+ // we're searching for.
+ return false;
+ }
+ }
+ return true;
+ }
+ });
+ }
+ }
+
+ boolean containsPrefixOf(IPath path) {
+ return this.mapPathsToRoots.containsPrefixOf(path);
+ }
+
+ @Override
+ public void cleanup() {
+ // No explicit cleanup required for this class
+ }
+
+ public static INameEnvironment create(List<IJavaProject> javaProjects, org.eclipse.jdt.core.ICompilationUnit[] copies) {
+ if (JavaIndex.isEnabled() && isEnabled()) {
+ return new IndexBasedJavaSearchEnvironment(javaProjects, copies);
+ } else {
+ Iterator<IJavaProject> next = javaProjects.iterator();
+ JavaSearchNameEnvironment result = new JavaSearchNameEnvironment(next.next(), copies);
+
+ while (next.hasNext()) {
+ result.addProjectClassPath((JavaProject)next.next());
+ }
+ return result;
+ }
+ }
+}
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java
index 9a115722f..0c7de1594 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/JavaSearchNameEnvironment.java
@@ -15,11 +15,15 @@ package org.eclipse.jdt.internal.core.search.matching;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
+import java.util.Map;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
-import org.eclipse.jdt.core.*;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageDeclaration;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
@@ -39,19 +43,24 @@ import org.eclipse.jdt.internal.core.util.Util;
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class JavaSearchNameEnvironment implements INameEnvironment, SuffixConstants {
-
+
LinkedHashSet<ClasspathLocation> locationSet;
/*
* A map from the fully qualified slash-separated name of the main type (String) to the working copy
*/
- HashMap workingCopies;
-
+ Map<String, org.eclipse.jdt.core.ICompilationUnit> workingCopies;
+
public JavaSearchNameEnvironment(IJavaProject javaProject, org.eclipse.jdt.core.ICompilationUnit[] copies) {
this.locationSet = computeClasspathLocations((JavaProject) javaProject);
+ this.workingCopies = getWorkingCopyMap(copies);
+}
+
+public static Map<String, org.eclipse.jdt.core.ICompilationUnit> getWorkingCopyMap(
+ org.eclipse.jdt.core.ICompilationUnit[] copies) {
+ int length = copies == null ? 0 : copies.length;
+ HashMap<String, org.eclipse.jdt.core.ICompilationUnit> result = new HashMap<>(length);
try {
- int length = copies == null ? 0 : copies.length;
- this.workingCopies = new HashMap(length);
if (copies != null) {
for (int i = 0; i < length; i++) {
org.eclipse.jdt.core.ICompilationUnit workingCopy = copies[i];
@@ -60,12 +69,13 @@ public JavaSearchNameEnvironment(IJavaProject javaProject, org.eclipse.jdt.core.
String cuName = workingCopy.getElementName();
String mainTypeName = Util.getNameWithoutJavaLikeExtension(cuName);
String qualifiedMainTypeName = pkg.length() == 0 ? mainTypeName : pkg.replace('.', '/') + '/' + mainTypeName;
- this.workingCopies.put(qualifiedMainTypeName, workingCopy);
+ result.put(qualifiedMainTypeName, workingCopy);
}
}
} catch (JavaModelException e) {
// working copy doesn't exist: cannot happen
}
+ return result;
}
public void cleanup() {
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java
index 48094d331..1630f54f9 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java
@@ -294,11 +294,11 @@ private static HashMap workingCopiesThatCanSeeFocus(org.eclipse.jdt.core.ICompil
return result;
}
-public static ClassFileReader classFileReader(IType type) {
+public static IBinaryType classFileReader(IType type) {
IClassFile classFile = type.getClassFile();
JavaModelManager manager = JavaModelManager.getJavaModelManager();
if (classFile.isOpen())
- return (ClassFileReader) manager.getInfo(type);
+ return (IBinaryType)manager.getInfo(type);
PackageFragment pkg = (PackageFragment) type.getPackageFragment();
IPackageFragmentRoot root = (IPackageFragmentRoot) pkg.getParent();
@@ -549,7 +549,7 @@ protected IJavaElement createHandle(AbstractMethodDeclaration method, IJavaEleme
if (type.isBinary()) {
// don't cache the methods of the binary type
// fall thru if its a constructor with a synthetic argument... find it the slower way
- ClassFileReader reader = classFileReader(type);
+ IBinaryType reader = classFileReader(type);
if (reader != null) {
// build arguments names
boolean firstIsSynthetic = false;
@@ -619,7 +619,7 @@ protected IJavaElement createHandle(AbstractMethodDeclaration method, IJavaEleme
* Create binary method handle
*/
IMethod createBinaryMethodHandle(IType type, char[] methodSelector, char[][] argumentTypeNames) {
- ClassFileReader reader = MatchLocator.classFileReader(type);
+ IBinaryType reader = MatchLocator.classFileReader(type);
if (reader != null) {
IBinaryMethod[] methods = reader.getMethods();
if (methods != null) {
@@ -1271,9 +1271,15 @@ public void initialize(JavaProject project, int possibleMatchSize) throws JavaMo
SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(this.workingCopies);
- this.nameEnvironment = new JavaSearchNameEnvironment(project, this.workingCopies);
- if (this.pattern.focus != null)
- ((JavaSearchNameEnvironment) this.nameEnvironment).addProjectClassPath((JavaProject) this.pattern.focus.getJavaProject());
+ List<IJavaProject> projects = new ArrayList<>();
+ projects.add(project);
+ if (this.pattern.focus != null) {
+ IJavaProject focusProject = this.pattern.focus.getJavaProject();
+ if (focusProject != project) {
+ projects.add(focusProject);
+ }
+ }
+ this.nameEnvironment = IndexBasedJavaSearchEnvironment.create(projects, this.workingCopies);
// create lookup environment
Map map = project.getOptions(true);
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java
index 3cd748ce1..88f084a64 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/PossibleMatch.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2012 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -15,7 +15,7 @@ import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.search.SearchDocument;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
-import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.core.*;
import org.eclipse.jdt.internal.core.util.Util;
@@ -135,7 +135,7 @@ private String getSourceFileName() {
this.sourceFileName = NO_SOURCE_FILE_NAME;
if (this.openable.getSourceMapper() != null) {
BinaryType type = (BinaryType) ((ClassFile) this.openable).getType();
- ClassFileReader reader = MatchLocator.classFileReader(type);
+ IBinaryType reader = MatchLocator.classFileReader(type);
if (reader != null) {
String fileName = type.sourceFileName(reader);
this.sourceFileName = fileName == null ? NO_SOURCE_FILE_NAME : fileName;
diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java
index 6351839ee..71bc06163 100644
--- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java
+++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/processing/JobManager.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -201,7 +201,7 @@ public abstract class JobManager implements Runnable {
case IJob.WaitUntilReady :
int totalWork = 1000;
- SubMonitor subProgress = subMonitor.setWorkRemaining(10).split(8).setWorkRemaining(totalWork);
+ SubMonitor waitMonitor = subMonitor.setWorkRemaining(10).split(8).setWorkRemaining(totalWork);
// use local variable to avoid potential NPE (see bug 20435 NPE when searching java method
// and bug 42760 NullPointerException in JobManager when searching)
Thread t = this.processingThread;
@@ -218,30 +218,28 @@ public abstract class JobManager implements Runnable {
float lastWorked = 0;
float totalWorked = 0;
while ((awaitingJobsCount = awaitingJobsCount()) > 0) {
- if (subProgress.isCanceled() || this.processingThread == null)
+ if (waitMonitor.isCanceled() || this.processingThread == null)
throw new OperationCanceledException();
IJob currentJob = currentJob();
// currentJob can be null when jobs have been added to the queue but job manager is not enabled
if (currentJob != null && currentJob != previousJob) {
if (VERBOSE)
Util.verbose("-> NOT READY - waiting until ready - " + searchJob);//$NON-NLS-1$
- if (subProgress != null) {
- String indexing = Messages.bind(Messages.jobmanager_filesToIndex, currentJob.getJobFamily(), Integer.toString(awaitingJobsCount));
- subProgress.subTask(indexing);
- // ratio of the amount of work relative to the total work
- float ratio = awaitingJobsCount < totalWork ? 1 : ((float) totalWork) / awaitingJobsCount;
- if (lastJobsCount > awaitingJobsCount) {
- totalWorked += (lastJobsCount - awaitingJobsCount) * ratio;
- } else {
- // more jobs were added, just increment by the ratio
- totalWorked += ratio;
- }
- if (totalWorked - lastWorked >= 1) {
- subProgress.worked((int) (totalWorked - lastWorked));
- lastWorked = totalWorked;
- }
- lastJobsCount = awaitingJobsCount;
+ String indexing = Messages.bind(Messages.jobmanager_filesToIndex, currentJob.getJobFamily(), Integer.toString(awaitingJobsCount));
+ waitMonitor.subTask(indexing);
+ // ratio of the amount of work relative to the total work
+ float ratio = awaitingJobsCount < totalWork ? 1 : ((float) totalWork) / awaitingJobsCount;
+ if (lastJobsCount > awaitingJobsCount) {
+ totalWorked += (lastJobsCount - awaitingJobsCount) * ratio;
+ } else {
+ // more jobs were added, just increment by the ratio
+ totalWorked += ratio;
}
+ if (totalWorked - lastWorked >= 1) {
+ waitMonitor.worked((int) (totalWorked - lastWorked));
+ lastWorked = totalWorked;
+ }
+ lastJobsCount = awaitingJobsCount;
previousJob = currentJob;
}
try {

Back to the top