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