Skip to main content
summaryrefslogtreecommitdiffstats
blob: 1a6b369c77bc2f48829be939b902160cd9093253 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*******************************************************************************
 * Copyright (c) 2015, 2018 Google, Inc and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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,
						null, // TODO(SHMOD): don't have a module name, but while looking in unindexed classpath locations, this is probably OK
						qSourceFileName,  // doesn't include the file extension
						false,
						null /*no module filtering on source dir*/);
				}
			} 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,
						null,  // TODO(SHMOD): don't have a module name, but while looking in unindexed classpath locations, this is probably OK
						qBinaryFileName,
						false,
						null /*no module filtering, this env is not module aware*/);
			}
			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;
		}
	}
}

Back to the top