blob: 631c3847ab804a865a71483d28843a91b0909b2a [file] [log] [blame]
/*******************************************************************************
* 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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResourceStatus;
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.IStatus;
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.indexer.Indexer;
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);
}
public static ClassFileReader rawReadType(BinaryTypeDescriptor descriptor, boolean fullyInitialize) throws JavaModelException, ClassFormatException {
try {
return rawReadTypeTestForExists(descriptor, fullyInitialize, true);
} catch (FileNotFoundException e) {
throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
}
}
/**
* 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
* @throws FileNotFoundException if the file does not exist
*/
public static ClassFileReader rawReadTypeTestForExists(BinaryTypeDescriptor descriptor, boolean fullyInitialize,
boolean useInvalidArchiveCache) throws JavaModelException, ClassFormatException, FileNotFoundException {
if (descriptor == null) {
return null;
}
if (descriptor.isInJarFile()) {
ZipFile zip = null;
try {
zip = JavaModelManager.getJavaModelManager().getZipFile(new Path(new String(descriptor.workspacePath)),
useInvalidArchiveCache);
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;
try (InputStream stream = file.getContents(true)) {
contents = org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsByteArray(stream, -1);
} catch (CoreException e) {
IStatus status = e.getStatus();
if (status.getCode() == IResourceStatus.RESOURCE_NOT_FOUND) {
throw new FileNotFoundException();
}
throw new JavaModelException(e);
} catch (IOException e) {
throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
}
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) {
Package.log("Index corruption detected. Rebuilding index.", e); //$NON-NLS-1$
Indexer.getInstance().requestRebuildIndex();
}
}
}
throw new NotInIndexException();
}
}