| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.java.source.ui; |
| |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.*; |
| import java.util.logging.Logger; |
| import java.util.logging.Level; |
| import java.util.regex.Pattern; |
| import javax.lang.model.element.TypeElement; |
| import javax.swing.Icon; |
| import org.apache.lucene.document.Document; |
| import org.netbeans.api.annotations.common.CheckForNull; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.queries.SourceForBinaryQuery; |
| import org.netbeans.api.java.source.ClassIndex; |
| import org.netbeans.api.java.source.ClassIndex.NameKind; |
| import org.netbeans.api.java.source.ClassIndex.SearchScope; |
| import org.netbeans.api.java.source.ClassIndex.SearchScopeType; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.SourceUtils; |
| import org.netbeans.api.java.source.ui.TypeElementFinder; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectInformation; |
| import org.netbeans.modules.java.BinaryElementOpen; |
| import org.netbeans.modules.java.source.usages.ClassIndexImpl; |
| import org.netbeans.modules.java.source.usages.ClassIndexImplEvent; |
| import org.netbeans.modules.java.source.usages.ClassIndexImplListener; |
| import org.netbeans.modules.java.source.usages.ClassIndexManager; |
| import org.netbeans.modules.java.source.usages.DocumentUtil; |
| import org.netbeans.modules.parsing.lucene.support.Convertor; |
| import org.netbeans.modules.parsing.lucene.support.Index; |
| import org.netbeans.modules.parsing.lucene.support.IndexManager; |
| import org.netbeans.modules.parsing.lucene.support.IndexManager.Action; |
| import org.netbeans.modules.parsing.lucene.support.Queries; |
| import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport; |
| import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.netbeans.spi.jumpto.support.NameMatcherFactory; |
| import org.netbeans.spi.jumpto.type.SearchType; |
| import org.netbeans.spi.jumpto.type.TypeProvider; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.URLMapper; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| import org.openide.util.Parameters; |
| |
| /** |
| * |
| * @author Petr Hrebejk |
| * @author Tomas Zezula |
| * @author markiewb |
| */ |
| @org.openide.util.lookup.ServiceProvider(service=org.netbeans.spi.jumpto.type.TypeProvider.class) |
| public class JavaTypeProvider implements TypeProvider { |
| private static final Logger LOGGER = Logger.getLogger(JavaTypeProvider.class.getName()); |
| private static final Level LEVEL = Level.FINE; |
| private static final Collection<? extends JavaTypeDescription> ACTIVE = Collections.unmodifiableSet(new HashSet<JavaTypeDescription>()); //Don't replace with C.emptySet() has to be identity unique. |
| |
| //@NotThreadSafe //Confinement within a thread |
| private Map<URI,CacheItem> rootCache; |
| |
| private volatile boolean isCanceled = false; |
| private ClasspathInfo cpInfo; |
| |
| private final TypeElementFinder.Customizer customizer; |
| |
| @Override |
| public String name() { |
| return "java"; // NOI18N |
| } |
| |
| @Override |
| public String getDisplayName() { |
| // TODO - i18n |
| return "Java Classes"; |
| } |
| |
| @Override |
| public void cleanup() { |
| isCanceled = false; |
| DataCache.clear(); |
| setRootCache(null); |
| } |
| |
| @Override |
| public void cancel() { |
| isCanceled = true; |
| } |
| |
| public JavaTypeProvider() { |
| this(null, null); |
| } |
| |
| public JavaTypeProvider(ClasspathInfo cpInfo, TypeElementFinder.Customizer customizer) { |
| this.cpInfo = cpInfo; |
| this.customizer = customizer; |
| } |
| |
| @Override |
| public void computeTypeNames(Context context, final Result res) { |
| isCanceled = false; |
| String originalText = context.getText(); |
| SearchType searchType = context.getSearchType(); |
| |
| final DataCache dataCache = DataCache.forText(originalText, searchType); |
| assert dataCache != null; |
| final CacheItem.DataCacheCallback callBack = new CacheItem.DataCacheCallback() { |
| @Override |
| public void handleDataCacheChange(@NonNull final CacheItem ci) { |
| assert ci != null; |
| dataCache.put(ci, null); |
| } |
| }; |
| |
| boolean hasBinaryOpen = Lookup.getDefault().lookup(BinaryElementOpen.class) != null; |
| final ClassIndex.NameKind nameKind; |
| switch (searchType) { |
| case EXACT_NAME: nameKind = ClassIndex.NameKind.SIMPLE_NAME; break; |
| case CASE_INSENSITIVE_EXACT_NAME: nameKind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP; break; |
| case PREFIX: nameKind = ClassIndex.NameKind.PREFIX; break; |
| case CASE_INSENSITIVE_PREFIX: nameKind = ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX; break; |
| case REGEXP: nameKind = ClassIndex.NameKind.REGEXP; break; |
| case CASE_INSENSITIVE_REGEXP: nameKind = ClassIndex.NameKind.CASE_INSENSITIVE_REGEXP; break; |
| case CAMEL_CASE: nameKind = ClassIndex.NameKind.CAMEL_CASE; break; |
| case CASE_INSENSITIVE_CAMEL_CASE: nameKind = ClassIndex.NameKind.CAMEL_CASE_INSENSITIVE; break; |
| default: throw new RuntimeException("Unexpected search type: " + searchType); |
| } |
| |
| if (getRootCache() == null) { |
| Map<URI,CacheItem> sources = null; |
| |
| if (cpInfo == null) { |
| sources = new HashMap<>(); |
| |
| // Sources - ClassPath.SOURCE and translated ClassPath.COMPILE & ClassPath.BOOT |
| Collection<FileObject> srcRoots = QuerySupport.findRoots( |
| (Project)null, |
| Collections.singleton(ClassPath.SOURCE), |
| Collections.<String>emptySet(), |
| Collections.<String>emptySet()); |
| |
| for(FileObject root : srcRoots) { |
| if ( isCanceled ) { |
| return; |
| } |
| final URL rootUrl = root.toURL(); |
| if ( isCanceled ) { |
| return; |
| } else { |
| try { |
| sources.put(rootUrl.toURI(), new CacheItem( rootUrl, ClassPath.SOURCE, callBack)); |
| } catch (URISyntaxException ex) { |
| LOGGER.log(Level.INFO, "Cannot convert root {0} into URI, ignoring.", rootUrl); //NOI18N |
| } |
| } |
| } |
| |
| // Binaries - not translated ClassPath.COMPILE & ClassPath.BOOT |
| Collection<FileObject> binRoots = QuerySupport.findRoots( |
| (Project)null, |
| Collections.<String>emptySet(), |
| Collections.<String>emptySet(), |
| Arrays.asList(new String [] { ClassPath.COMPILE, ClassPath.BOOT})); |
| |
| for(FileObject root : binRoots) { |
| if ( isCanceled ) { |
| return; |
| } |
| URL rootUrl = root.toURL(); |
| if (!hasBinaryOpen) { |
| SourceForBinaryQuery.Result result = SourceForBinaryQuery.findSourceRoots(rootUrl); |
| if ( result.getRoots().length == 0 ) { |
| continue; |
| } |
| } |
| if ( isCanceled ) { |
| return; |
| } |
| else { |
| try { |
| final URI rootURI = rootUrl.toURI(); |
| if (!sources.containsKey(rootURI)) { |
| sources.put(rootURI, new CacheItem( rootUrl, ClassPath.BOOT, callBack)); |
| } |
| } catch (URISyntaxException ex) { |
| LOGGER.log(Level.INFO, "Cannot convert root {0} into URI, ignoring.", rootUrl); //NOI18N |
| } |
| } |
| } |
| } else { // user provided classpath |
| final List<ClassPath.Entry> bootRoots = cpInfo.getClassPath(ClasspathInfo.PathKind.BOOT).entries(); |
| final List<ClassPath.Entry> compileRoots = cpInfo.getClassPath(ClasspathInfo.PathKind.COMPILE).entries(); |
| final List<ClassPath.Entry> sourceRoots = cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE).entries(); |
| sources = new HashMap<>(bootRoots.size() + compileRoots.size() + sourceRoots.size()); |
| |
| // bootPath |
| final String[] cpType = new String[1]; |
| for (ClassPath.Entry entry : bootRoots) { |
| if ( isCanceled ) { |
| return; |
| } |
| cpType[0] = ClassPath.BOOT; |
| for (URL root : translate(entry.getURL(), cpType)) { |
| try { |
| sources.put(root.toURI(), new CacheItem(root, cpType[0], callBack)); |
| } catch (URISyntaxException ex) { |
| LOGGER.log(Level.INFO, "Cannot convert root {0} into URI, ignoring.", entry.getURL()); //NOI18N |
| } |
| } |
| } |
| |
| // classPath |
| for (ClassPath.Entry entry : compileRoots) { |
| if ( isCanceled ) { |
| return; |
| } |
| cpType[0] = ClassPath.COMPILE; |
| for (URL root : translate(entry.getURL(), cpType)) { |
| try { |
| sources.put(root.toURI(), new CacheItem(root, cpType[0], callBack)); |
| } catch (URISyntaxException ex) { |
| LOGGER.log(Level.INFO, "Cannot convert root {0} into URI, ignoring.", entry.getURL()); //NOI18N |
| } |
| } |
| } |
| |
| // sourcePath |
| for (ClassPath.Entry entry : sourceRoots) { |
| if ( isCanceled ) { |
| return; |
| } |
| try { |
| sources.put(entry.getURL().toURI(), new CacheItem(entry.getURL(),ClassPath.SOURCE, callBack)); |
| } catch (URISyntaxException ex) { |
| LOGGER.log(Level.INFO, "Cannot convert root {0} into URI, ignoring.", entry.getURL()); //NOI18N |
| } |
| } |
| } |
| |
| if ( !isCanceled ) { |
| // cache = sources; |
| if (LOGGER.isLoggable(LEVEL)) { |
| LOGGER.log(LEVEL, "Querying following roots:"); //NOI18N |
| for(CacheItem ci : sources.values()) { |
| LOGGER.log(LEVEL, " {0}; binary={1}", new Object[]{ci.getRoot().toURI(), ci.isBinary()}); //NOI18N |
| } |
| LOGGER.log(LEVEL, "-------------------------"); //NOI18N |
| } |
| setRootCache(sources); |
| } |
| else { |
| return; |
| } |
| |
| } |
| |
| final Map<URI,CacheItem> c = getRootCache(); |
| if (c == null) return; |
| final ArrayList<JavaTypeDescription> types = new ArrayList<>(c.size() * 20); |
| |
| // is scan in progress? If so, provide a message to user. |
| final boolean scanInProgress = SourceUtils.isScanInProgress(); |
| if (scanInProgress) { |
| // ui message |
| final String warningKind = NbBundle.getMessage(JavaTypeProvider.class, "LBL_TypeKind"); |
| final String message = NbBundle.getMessage(JavaTypeProvider.class, "LBL_ScanInProgress_warning", warningKind); |
| res.setMessage(message); |
| } else { |
| res.setMessage(null); |
| } |
| int lastIndexOfDot = originalText.lastIndexOf("."); //NOI18N |
| boolean isFullyQualifiedName = -1 != lastIndexOfDot; |
| final Pattern packageName; |
| final String typeName; |
| if (isFullyQualifiedName) { |
| packageName = createPackageRegExp(originalText.substring(0, lastIndexOfDot)); |
| typeName = originalText.substring(lastIndexOfDot + 1); |
| res.setHighlightText(typeName); |
| } else { |
| packageName = null; |
| typeName = originalText; |
| } |
| final String textForQuery = getTextForQuery(typeName, nameKind, context.getSearchType()); |
| |
| LOGGER.log(Level.FINE, "Text For Query ''{0}''.", originalText); |
| if (customizer != null) { |
| for (final CacheItem ci : c.values()) { |
| final Set<ElementHandle<TypeElement>> names = new HashSet<> (customizer.query( |
| ci.getClasspathInfo(), textForQuery, nameKind, //Needs to pass slow cpinfo to keep compatibility |
| EnumSet.of(ci.isBinary ? ClassIndex.SearchScope.DEPENDENCIES : ClassIndex.SearchScope.SOURCE) |
| )); |
| if (nameKind == ClassIndex.NameKind.CAMEL_CASE) { |
| names.addAll(customizer.query( |
| ci.getClasspathInfo(), textForQuery, ClassIndex.NameKind.CASE_INSENSITIVE_PREFIX, //Needs to pass slow cpinfo to keep compatibility |
| EnumSet.of(ci.isBinary ? ClassIndex.SearchScope.DEPENDENCIES : ClassIndex.SearchScope.SOURCE) |
| )); |
| } |
| for (ElementHandle<TypeElement> name : names) { |
| ci.initIndex(); |
| JavaTypeDescription td = new JavaTypeDescription(ci, name); |
| types.add(td); |
| if (isCanceled) { |
| return; |
| } |
| } |
| } |
| } else { |
| final Collection<CacheItem> nonCached = new ArrayDeque<>(c.size()); |
| for (CacheItem ci : c.values()) { |
| Collection<? extends JavaTypeDescription> cacheLine = dataCache.get(ci); |
| if (cacheLine != null) { |
| types.addAll(cacheLine); |
| } else { |
| nonCached.add(ci); |
| } |
| } |
| if (!nonCached.isEmpty()) { |
| try { |
| //Perform queries in single readAccess to suspend RU for all queries. |
| IndexManager.priorityAccess(new Action<Void>() { |
| @Override |
| public Void run() throws IOException, InterruptedException { |
| for (final CacheItem ci : nonCached) { |
| if (isCanceled) { |
| return null; |
| } |
| try { |
| final Collection<JavaTypeDescription> ct = new ArrayList<>(); |
| boolean exists = false; |
| //WB(dataCache[ci], ACTIVE) |
| dataCache.put(ci, ACTIVE); |
| try { |
| exists = ci.collectDeclaredTypes(packageName, textForQuery,nameKind, ct); |
| if (exists) { |
| types.addAll(ct); |
| } |
| } finally { |
| if (exists) { |
| //CAS(dataCache[ci], ACTIVE, ct) |
| dataCache.compareAndSet( |
| ci, |
| ACTIVE, |
| ct.isEmpty() ? Collections.<JavaTypeDescription>emptySet() : ct); |
| } else { |
| //WB(dataCache[ci], NULL) |
| dataCache.put(ci, null); |
| } |
| } |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| } catch (InterruptedException ie) { |
| //Never happens |
| throw new AssertionError(ie); |
| } |
| } |
| return null; |
| } |
| }); |
| } catch (IOException | InterruptedException ex) { |
| //Never happens |
| throw new AssertionError(ex); |
| } |
| } |
| if ( isCanceled ) { |
| return; |
| } |
| if (scanInProgress) { |
| res.pendingResult(); |
| } |
| } |
| if ( !isCanceled ) { |
| // Sorting is now done on the Go To Tpe dialog side |
| // Collections.sort(types); |
| res.addResult(types); |
| } |
| } |
| |
| static String removeNonJavaChars(String text) { |
| StringBuilder sb = new StringBuilder(); |
| |
| for( int i = 0; i < text.length(); i++) { |
| char c = text.charAt(i); |
| if( Character.isJavaIdentifierPart(c) || c == '*' || c == '?') { |
| sb.append(c); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| @CheckForNull |
| private Map<URI, CacheItem> getRootCache() { |
| if (LOGGER.isLoggable(LEVEL) && rootCache == null) { |
| LOGGER.log(LEVEL, "Returning null cache entries.", new Exception()); |
| } |
| return rootCache == null ? null : Collections.<URI, CacheItem>unmodifiableMap(rootCache); |
| } |
| |
| private void setRootCache(@NullAllowed final Map<URI,CacheItem> cache) { |
| if (LOGGER.isLoggable(LEVEL)) { |
| LOGGER.log(LEVEL, "Setting cache entries from " + this.rootCache + " to " + cache + ".", new Exception()); |
| } |
| if (this.rootCache != null) { |
| for (CacheItem ci : rootCache.values()) { |
| ci.dispose(); |
| } |
| } |
| this.rootCache = cache; |
| } |
| |
| private static String getTextForQuery(String text, final NameKind nameKind, SearchType searchType) { |
| String textForQuery; |
| switch (nameKind) { |
| case REGEXP: |
| case CASE_INSENSITIVE_REGEXP: |
| textForQuery = NameMatcherFactory.wildcardsToRegexp(removeNonJavaChars(text), searchType != SearchType.CASE_INSENSITIVE_EXACT_NAME); |
| break; |
| default: |
| textForQuery = text; |
| } |
| return textForQuery; |
| } |
| |
| @CheckForNull |
| private static Pattern createPackageRegExp(@NonNull String pkgName) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("(.*\\.)?"); //NOI18N |
| boolean valid = false; |
| for (int i=0; i< pkgName.length(); i++) { |
| char c = pkgName.charAt(i); |
| if (Character.isJavaIdentifierPart(c)) { |
| sb.append(c); |
| valid = true; |
| } else if (c == '.') { //NOI18N |
| sb.append(".*\\."); //NOI18N |
| } else if (c == '*') { //NOI18N |
| sb.append(".*"); //NOI18N |
| } else if (c == '?') { //NOI18N |
| sb.append(".?"); //NOI18N |
| } |
| } |
| final Pattern p; |
| if (valid) { |
| sb.append(".*(\\..*)?"); //NOI18N |
| p = Pattern.compile(sb.toString()); |
| } else { |
| p = null; |
| } |
| LOGGER.log(Level.FINE, "Package pattern: {0}", p); //NOI18N |
| return p; |
| } |
| |
| /** |
| * Todo: Create an API and remove |
| */ |
| @NonNull |
| private static NameKind translateSearchType( |
| @NonNull String simpleName, |
| @NonNull final NameKind originalSearchType) { |
| if (originalSearchType == NameKind.SIMPLE_NAME || |
| originalSearchType == NameKind.CASE_INSENSITIVE_REGEXP) { |
| return originalSearchType; |
| } else if ((isAllUpper(simpleName) && simpleName.length() > 1) || Queries.isCamelCase(simpleName, null, null)) { |
| return isCaseSensitive(originalSearchType) ? NameKind.CAMEL_CASE : NameKind.CAMEL_CASE_INSENSITIVE; |
| } else if (containsWildCard(simpleName) != -1) { |
| return isCaseSensitive(originalSearchType) ? NameKind.REGEXP : NameKind.CASE_INSENSITIVE_REGEXP; |
| } else { |
| return isCaseSensitive(originalSearchType) ? NameKind.PREFIX : NameKind.CASE_INSENSITIVE_PREFIX; |
| } |
| } |
| |
| private static boolean isCaseSensitive(@NonNull final NameKind originalNameKind) { |
| switch (originalNameKind) { |
| case CAMEL_CASE_INSENSITIVE: |
| case CASE_INSENSITIVE_PREFIX: |
| case CASE_INSENSITIVE_REGEXP: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| private static int containsWildCard(@NonNull final String text) { |
| for( int i = 0; i < text.length(); i++ ) { |
| if ( text.charAt( i ) == '?' || text.charAt( i ) == '*' ) { // NOI18N |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| private static boolean isAllUpper(@NonNull final String text ) { |
| for( int i = 0; i < text.length(); i++ ) { |
| if ( !Character.isUpperCase( text.charAt( i ) ) ) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @NonNull |
| private static URL[] translate(@NonNull final URL root, @NonNull final String[] cpType) { |
| final SourceForBinaryQuery.Result2 res = SourceForBinaryQuery.findSourceRoots2(root); |
| FileObject[] roots; |
| if (res.preferSources() && (roots = res.getRoots()).length > 0) { |
| cpType[0] = ClassPath.SOURCE; |
| return asURLs(roots); |
| } |
| return new URL[] {root}; |
| } |
| |
| @NonNull |
| private static URL[] asURLs(@NonNull final FileObject... fos) { |
| final URL[] res = new URL[fos.length]; |
| for (int i=0; i<fos.length; i++) { |
| res[i] = fos[i].toURL(); |
| } |
| return res; |
| } |
| |
| //@NotTreadSafe |
| static final class CacheItem implements ClassIndexImplListener { |
| |
| private final URI rootURI; |
| private final boolean isBinary; |
| private final String cpType; |
| |
| private DataCacheCallback callBack; |
| private String projectName; |
| private Icon projectIcon; |
| private ClasspathInfo cpInfo; |
| private ClassIndexImpl index; |
| private FileObject cachedRoot; |
| |
| public CacheItem ( |
| @NullAllowed final URL root, |
| @NullAllowed final String cpType, |
| @NullAllowed final DataCacheCallback callBack) throws URISyntaxException { |
| this.cpType = cpType; |
| this.isBinary = ClassPath.BOOT.equals(cpType) || ClassPath.COMPILE.equals(cpType); |
| this.rootURI = root == null ? null : root.toURI(); |
| this.callBack = callBack; |
| } |
| |
| @Override |
| public int hashCode () { |
| return this.rootURI == null ? 0 : this.rootURI.hashCode(); |
| } |
| |
| @Override |
| public boolean equals (Object other) { |
| if (other == this) { |
| return true; |
| } |
| if (other instanceof CacheItem) { |
| CacheItem otherItem = (CacheItem) other; |
| return this.rootURI == null ? otherItem.rootURI == null : this.rootURI.equals(otherItem.rootURI); |
| } |
| return false; |
| } |
| |
| @CheckForNull |
| public FileObject getRoot() { |
| synchronized (this) { |
| if (cachedRoot != null) { |
| return cachedRoot; |
| } |
| } |
| final URL root = toURL(rootURI); |
| final FileObject _tmp = root == null ? |
| null : |
| URLMapper.findFileObject(root); |
| synchronized (this) { |
| if (cachedRoot == null) { |
| cachedRoot = _tmp; |
| } |
| } |
| return _tmp; |
| } |
| |
| public boolean isBinary() { |
| return isBinary; |
| } |
| |
| public synchronized String getProjectName() { |
| if ( !isBinary && projectName == null) { |
| initProjectInfo(); |
| } |
| return projectName; |
| } |
| |
| public synchronized Icon getProjectIcon() { |
| if ( !isBinary && projectIcon == null ) { |
| initProjectInfo(); |
| } |
| return projectIcon; |
| } |
| |
| public ClasspathInfo getClasspathInfo() { |
| if (cpInfo == null) { |
| final ClassPath cp = ClassPathSupport.createClassPath(toURL(rootURI)); |
| cpInfo = isBinary ? |
| ClassPath.BOOT.equals(cpType) ? |
| ClasspathInfo.create(cp,ClassPath.EMPTY,ClassPath.EMPTY): |
| ClasspathInfo.create(ClassPath.EMPTY,cp,ClassPath.EMPTY): |
| ClasspathInfo.create(ClassPath.EMPTY,ClassPath.EMPTY,cp); |
| } |
| return cpInfo; |
| } |
| |
| private boolean initIndex() { |
| if (index == null) { |
| final URL root = toURL(rootURI); |
| index = root == null ? |
| null : |
| ClassIndexManager.getDefault().getUsagesQuery(root, true); |
| if (index == null) { |
| return false; |
| } |
| index.addClassIndexImplListener(this); |
| } |
| return true; |
| } |
| |
| public boolean collectDeclaredTypes( |
| @NullAllowed final Pattern packageName, |
| @NonNull final String typeName, |
| @NonNull NameKind kind, |
| @NonNull Collection<? super JavaTypeDescription> collector) throws IOException, InterruptedException { |
| if (!initIndex()) { |
| return false; |
| } |
| final SearchScope baseSearchScope = isBinary ? ClassIndex.SearchScope.DEPENDENCIES : ClassIndex.SearchScope.SOURCE; |
| SearchScopeType searchScope; |
| if (packageName != null) { |
| //FQN |
| final Set<String> allPackages = new HashSet<>(); |
| index.getPackageNames("", false, allPackages); //NOI18N |
| final Set<? extends String> packages = filterPackages(packageName, allPackages); |
| searchScope = ClassIndex.createPackageSearchScope(baseSearchScope, packages.toArray(new String[packages.size()])); |
| kind = translateSearchType(typeName, kind); |
| } else { |
| //simple name |
| searchScope = baseSearchScope; |
| } |
| try { |
| index.getDeclaredElements( |
| typeName, |
| kind, |
| Collections.unmodifiableSet(Collections.<SearchScopeType>singleton(searchScope)), |
| DocumentUtil.declaredTypesFieldSelector(true, true), |
| new JavaTypeDescriptionConvertor(this), |
| collector); |
| } catch (Index.IndexClosedException ice) { |
| //Closed after put into rootCache, ignore |
| } |
| return true; |
| } |
| |
| @Override |
| public void typesAdded(@NonNull final ClassIndexImplEvent event) { |
| if (callBack != null) { |
| callBack.handleDataCacheChange(this); |
| } |
| } |
| |
| @Override |
| public void typesRemoved(@NonNull final ClassIndexImplEvent event) { |
| if (callBack != null) { |
| callBack.handleDataCacheChange(this); |
| } |
| } |
| |
| @Override |
| public void typesChanged(@NonNull final ClassIndexImplEvent event) { |
| if (callBack != null) { |
| callBack.handleDataCacheChange(this); |
| } |
| } |
| |
| @CheckForNull |
| URI getRootURI() { |
| return rootURI; |
| } |
| |
| @CheckForNull |
| ClassIndexImpl getClassIndex() { |
| return index; |
| } |
| |
| private void initProjectInfo() { |
| Project p = FileOwnerQuery.getOwner(this.rootURI); |
| if (p != null) { |
| final ProjectInformation pi = p.getLookup().lookup(ProjectInformation.class); //Intentionally does not use ProjectUtil.getInformation() as it does slow icon annotation |
| projectName = pi == null ? |
| p.getProjectDirectory().getNameExt() : |
| pi.getDisplayName(); |
| projectIcon = pi == null ? |
| null : |
| pi.getIcon(); |
| } |
| } |
| |
| @NonNull |
| private Set<? extends String> filterPackages( |
| @NonNull final Pattern packageName, |
| @NonNull final Set<? extends String> basePackages) { |
| final Set<String> result = new HashSet<>(); |
| for (String pkg : basePackages) { |
| if (packageName.matcher(pkg).matches()) { |
| result.add(pkg); |
| } |
| } |
| return result; |
| } |
| |
| private void dispose() { |
| callBack = null; |
| if (index != null) { |
| index.removeClassIndexImplListener(this); |
| } |
| } |
| |
| @CheckForNull |
| private static URL toURL(@NullAllowed final URI uri) { |
| try { |
| return uri == null ? |
| null : |
| uri.toURL(); |
| } catch (MalformedURLException ex) { |
| LOGGER.log( |
| Level.FINE, |
| "Cannot convert URI to URL", //NOI18N |
| ex); |
| return null; |
| } |
| } |
| |
| static interface DataCacheCallback { |
| void handleDataCacheChange(@NonNull final CacheItem ci); |
| } |
| |
| private static class JavaTypeDescriptionConvertor implements Convertor<Document, JavaTypeDescription> { |
| |
| private static final Pattern ANONYMOUS = Pattern.compile(".*\\$\\d+(\\$.+)?"); //NOI18N |
| private static final Convertor<Document,ElementHandle<TypeElement>> HANDLE_CONVERTOR = DocumentUtil.typeElementConvertor(); |
| private static final Convertor<Document,String> SOURCE_CONVERTOR = DocumentUtil.sourceNameConvertor(); |
| |
| private final CacheItem ci; |
| |
| JavaTypeDescriptionConvertor(@NonNull final CacheItem ci) { |
| this.ci = ci; |
| } |
| |
| @Override |
| public JavaTypeDescription convert(Document p) { |
| final String binName = DocumentUtil.getSimpleBinaryName(p); |
| //The regexp still needed for class files older than 1.5 which has no enclosingMethod attr |
| if (binName == null || ANONYMOUS.matcher(binName).matches() || DocumentUtil.isLocal(p)) { |
| return null; |
| } |
| final ElementHandle<TypeElement> eh = HANDLE_CONVERTOR.convert(p); |
| final String sourceName = SOURCE_CONVERTOR.convert(p); |
| return eh == null ? null : new JavaTypeDescription( |
| ci, |
| eh, |
| DocumentUtil.getSimpleName(p), |
| sourceName); |
| } |
| |
| } |
| |
| } |
| |
| //@ThreadSafe |
| private static final class DataCache { |
| |
| //@GuardedBy("DataCache.class") |
| private static String forText; |
| //@GuardedBy("DataCache.class") |
| private static final Map<SearchType,DataCache> instances = |
| new EnumMap<>(SearchType.class); |
| |
| //@GuardedBy("this") |
| private final Map<CacheItem,Collection<? extends JavaTypeDescription>> dataCache = new HashMap<>(); |
| |
| |
| private DataCache() {} |
| |
| @CheckForNull |
| synchronized Collection<? extends JavaTypeDescription> get(@NonNull final CacheItem item) { |
| return dataCache.get(item); |
| } |
| |
| synchronized void put ( |
| @NonNull final CacheItem item, |
| @NullAllowed final Collection<? extends JavaTypeDescription> data) { |
| dataCache.put(item, data); |
| } |
| |
| synchronized boolean compareAndSet( |
| @NonNull final CacheItem item, |
| @NullAllowed final Collection<? extends JavaTypeDescription> expected, |
| @NullAllowed final Collection<? extends JavaTypeDescription> update) { |
| if (dataCache.get(item) == expected) { |
| dataCache.put(item,update); |
| return true; |
| } |
| return false; |
| } |
| |
| static synchronized void clear() { |
| forText = null; |
| instances.clear(); |
| } |
| |
| @NonNull |
| static synchronized DataCache forText( |
| @NonNull final String text, |
| @NonNull final SearchType searchType) { |
| Parameters.notNull("text", text); //NOI18N |
| Parameters.notNull("searchType", searchType); //NOI18N |
| if (!text.equals(forText)) { |
| clear(); |
| forText = text; |
| } |
| DataCache cacheInstance = instances.get(searchType); |
| if (cacheInstance == null) { |
| cacheInstance = new DataCache(); |
| instances.put(searchType, cacheInstance); |
| } |
| return cacheInstance; |
| } |
| } |
| } |