/******************************************************************************* * 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; } } }