blob: c1240dbb754bb567939524d9031dc29fac7a335a [file] [log] [blame]
/*
* 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.refactoring.java;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.ClasspathInfo.PathKind;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.Parameters;
/**
* It is a temporary solution to improve the performance. Later, it should be
* moved to {@link SourceUtils} as API.
*
* @author Jan Pokorsky
*/
public final class SourceUtilsEx {
/**
* Returns a source file in which the passed element
* is declared in. This tuned up version of {@code SourceUtils.getFile}
* is necessary as sequential invocations of {@code SourceUtils.getFile} are
* excessively slow.
*
* @param element an element to find {@link FileObject} for
* @param cpInfo scope where the file will be searched
* @param cache a cache
* @return the source file
* @see SourceUtils#getFile(org.netbeans.api.java.source.ElementHandle, org.netbeans.api.java.source.ClasspathInfo) SourceUtils.getFile
*/
public static FileObject getFile(final Element element, final ClasspathInfo cpInfo, final Cache cache) {
Parameters.notNull("element", element); //NOI18N
Parameters.notNull("cpInfo", cpInfo); //NOI18N
Parameters.notNull("cache", cache); //NOI18N
Element current = element;
Element prev = current.getKind() == ElementKind.PACKAGE ? current : null;
while (current.getKind() != ElementKind.PACKAGE) {
prev = current;
current = current.getEnclosingElement();
}
if (prev == null) {
return null;
}
final ElementKind kind = prev.getKind();
String fqn;
if (kind.isClass() || kind.isInterface()) {
fqn = ((TypeElement) prev).getQualifiedName().toString();
} else if (kind == ElementKind.PACKAGE) {
fqn = ((PackageElement) prev).getQualifiedName().toString();
} else {
return null;
}
Object cached = cache.cacheOfSrcFiles.get(fqn);
if (cached == null) {
final ElementHandle<? extends Element> handle = ElementHandle.create(prev);
cached = SourceUtils.getFile(handle, cpInfo);
cache.cacheOfSrcFiles.put(fqn, cached != null ? cached : Cache.NULL);
} else if (cached == Cache.NULL) {
cached = null;
}
return (FileObject) cached;
}
/**
* Cached environment that helps to speed up
* {@link Element} to {@link FileObject} translations.
*/
public static final class Cache {
private static final Object NULL = new Object();
/**
* mapping of top level classes FQN to their source files
*/
private final Map<String, Object> cacheOfSrcFiles = new HashMap<String, Object>();
}
/**
* Returns a collection of source files in which the passed handles
* are declared. This tuned up version of {@code SourceUtils.getFile}
* is necessary as sequential calls to {@code SourceUtils.getFile} are
* excessively slow.
*
* @param handles to find {@link FileObject}s for
* @param cpInfo classpaths for resolving handle
* @return collection of {@link FileObject}s
* @see SourceUtils#getFile(org.netbeans.api.java.source.ElementHandle, org.netbeans.api.java.source.ClasspathInfo) SourceUtils.getFile
*/
public static Collection<FileObject> getFiles(final Collection<ElementHandle<? extends Element>> handles, final ClasspathInfo cpInfo, AtomicBoolean cancel) {
Parameters.notNull("handle", handles);
Parameters.notNull("cpInfo", cpInfo);
List<FileObject> result = new ArrayList<FileObject>(handles.size());
Map<String, List<ResolvedElementHandle>> handlesPerPackages = new HashMap<String, List<ResolvedElementHandle>>();
try {
ClassPath cp = ClassPathSupport.createProxyClassPath(
new ClassPath[] {
cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE),
createClassPath(cpInfo,ClasspathInfo.PathKind.BOOT),
createClassPath(cpInfo,ClasspathInfo.PathKind.COMPILE),
});
for (ElementHandle<? extends Element> handle : handles) {
if(cancel != null && cancel.get()) {
return Collections.<FileObject>emptySet();
}
ResolvedElementHandle resolvedEHandle = ResolvedElementHandle.create(handle);
List<ResolvedElementHandle> l = handlesPerPackages.get(resolvedEHandle.pkgName);
if (l == null) {
l = new ArrayList<ResolvedElementHandle>();
handlesPerPackages.put(resolvedEHandle.pkgName, l);
}
l.add(resolvedEHandle);
}
for (Map.Entry<String, List<ResolvedElementHandle>> entry : handlesPerPackages.entrySet()) {
searchFiles(entry.getKey(), entry.getValue(), cp, result);
}
} catch (IOException e) {
Exceptions.printStackTrace(e);
}
return result;
}
private static final class ResolvedElementHandle {
String pkgName;
String className;
String[] signature;
boolean pkg;
private String sourceFileName;
static ResolvedElementHandle create(ElementHandle<? extends Element> handle) {
ResolvedElementHandle reh = new ResolvedElementHandle();
reh.pkg = handle.getKind() == ElementKind.PACKAGE;
reh.signature = getElementHandleSignature(handle);
assert reh.signature.length >= 1;
if (reh.pkg) {
reh.pkgName = convertPackage2Folder(reh.signature[0]);
} else {
int index = reh.signature[0].lastIndexOf('.'); //NOI18N
if (index < 0) {
reh.pkgName = ""; //NOI18N
reh.className = reh.signature[0];
} else {
reh.pkgName = convertPackage2Folder(reh.signature[0].substring(0, index));
reh.className = reh.signature[0].substring(index + 1);
}
}
return reh;
}
public String getSourceFileName() {
if (sourceFileName == null) {
sourceFileName = SourceUtilsEx.getSourceFileName(className);
}
return sourceFileName;
}
}
private static void searchFiles(String pkgName, List<ResolvedElementHandle> handles, ClassPath cp, List<FileObject> result) throws IOException {
List<FileObject> fos = cp.findAllResources(pkgName);
for (FileObject fo : fos) {
FileObject root = cp.findOwnerRoot(fo);
assert root != null;
FileObject[] sourceRoots = SourceForBinaryQuery.findSourceRoots(root.getURL()).getRoots();
ClassPath sourcePath = ClassPathSupport.createClassPath(sourceRoots);
LinkedList<FileObject> folders = new LinkedList<FileObject>(sourcePath.findAllResources(pkgName));
for (Iterator<ResolvedElementHandle> it = handles.iterator(); it.hasNext();) {
ResolvedElementHandle handle = it.next();
if (handle.pkg) {
result.add(folders.isEmpty() ? fo : folders.get(0));
it.remove();
break;
}
}
if (handles.isEmpty()) {
return;
}
boolean caseSensitive = isCaseSensitive();
folders.addFirst(fo);
for (FileObject folder : folders) {
FileObject[] children = folder.getChildren();
for (FileObject child : children) {
searchChildren(child, handles, result, caseSensitive);
if (handles.isEmpty()) {
return;
}
}
}
for (Iterator<ResolvedElementHandle> it = handles.iterator(); it.hasNext();) {
ResolvedElementHandle handle = it.next();
FileObject foundFo;
if (sourceRoots.length == 0) {
foundFo = findSource(handle.signature[0], root);
} else {
foundFo = findSource(handle.signature[0], sourceRoots);
}
if (foundFo != null) {
it.remove();
result.add(foundFo);
}
}
}
}
private static void searchChildren(FileObject child, List<ResolvedElementHandle> handles, List<FileObject> result, boolean caseSensitive) {
for (Iterator<ResolvedElementHandle> it = handles.iterator(); it.hasNext();) {
ResolvedElementHandle handle = it.next();
if (((caseSensitive && child.getName().equals(handle.getSourceFileName())) ||
(!caseSensitive && child.getName().equalsIgnoreCase(handle.getSourceFileName()))) &&
(child.isData() && ("java".equalsIgnoreCase(child.getExt()) || "class".equalsIgnoreCase(child.getExt())))) {
it.remove();
result.add(child);
}
}
}
/** copied
* @see SourceUtils#isCaseSensitive
*/
private static boolean isCaseSensitive () {
return ! new File ("a").equals (new File ("A")); //NOI18N
}
/** copied
* @see SourceUtils#getSourceFileName(java.lang.String)
*/
private static String getSourceFileName(String classFileName) {
int index = classFileName.indexOf('$');
return index == -1 ? classFileName : classFileName.substring(0, index);
}
/** helper method
* @see ElementHandle#getSignature
*/
private static String[] getElementHandleSignature(ElementHandle handle) {
try {
Method method = ElementHandle.class.getDeclaredMethod("getSignature"); //NOI18N
method.setAccessible(true);
return (String[]) method.invoke(handle);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
/** helper method
* @see SourceUtils#createClassPath(org.netbeans.api.java.source.ClasspathInfo, org.netbeans.api.java.source.ClasspathInfo.PathKind)
*/
private static ClassPath createClassPath(ClasspathInfo cpInfo, PathKind kind) {
try {
Method method = SourceUtils.class.getDeclaredMethod("createClassPath", ClasspathInfo.class, PathKind.class); //NOI18N
method.setAccessible(true);
return (ClassPath) method.invoke(null, cpInfo, kind);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
/** helper method
* @see SourceUtils#findSource(java.lang.String, org.openide.filesystems.FileObject[])
*/
private static FileObject findSource(final String binaryName, final FileObject... fos) throws IOException {
try {
Method method = SourceUtils.class.getDeclaredMethod("findSource", String.class, FileObject[].class); //NOI18N
method.setAccessible(true);
return (FileObject) method.invoke(null, binaryName, fos);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof IOException) {
throw (IOException) ex.getCause();
}
throw new IllegalStateException(ex);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
/** copied
* @see org.netbeans.modules.java.source.parsing.FileObjects#convertPackage2Folder(java.lang.String)
*/
private static String convertPackage2Folder( String packageName ) {
return packageName.replace( '.', '/' );
}
}