blob: 253de8d8967737ef589d11c8deb695a24ff20fd0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Stephan Herrmann - Contribution for
* Bug 458577 - IClassFile.getWorkingCopy() may lead to NPE in BecomeWorkingCopyOperation
* Bug 440477 - [null] Infrastructure for feeding external annotations into compilation
* Bug 462768 - [null] NPE when using linked folder for external annotations
*******************************************************************************/
package org.eclipse.jdt.internal.core;
import java.io.File;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
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.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.util.Util;
/**
* Common parts of ClassFile (containing a BinaryType) and ModularClassFile (containing a BinaryModule).
* Prior to Java 9, most of this content was directly in ClassFile.
*/
public abstract class AbstractClassFile extends Openable implements IClassFile, SuffixConstants {
protected String name;
protected AbstractClassFile(PackageFragment parent, String nameWithoutExtension) {
super(parent);
this.name = nameWithoutExtension;
}
/*
* @see IClassFile#becomeWorkingCopy(IProblemRequestor, WorkingCopyOwner, IProgressMonitor)
*/
@Override
public ICompilationUnit becomeWorkingCopy(IProblemRequestor problemRequestor, WorkingCopyOwner owner, IProgressMonitor monitor) throws JavaModelException {
JavaModelManager manager = JavaModelManager.getJavaModelManager();
CompilationUnit workingCopy = new ClassFileWorkingCopy(this, owner == null ? DefaultWorkingCopyOwner.PRIMARY : owner);
JavaModelManager.PerWorkingCopyInfo perWorkingCopyInfo = manager.getPerWorkingCopyInfo(workingCopy, false/*don't create*/, true /*record usage*/, null/*no problem requestor needed*/);
if (perWorkingCopyInfo == null) {
// close cu and its children
close();
BecomeWorkingCopyOperation operation = new BecomeWorkingCopyOperation(workingCopy, problemRequestor);
operation.runOperation(monitor);
return workingCopy;
}
return perWorkingCopyInfo.workingCopy;
}
/**
* @see ICodeAssist#codeComplete(int, ICompletionRequestor)
* @deprecated
*/
@Override
@Deprecated
public void codeComplete(int offset, ICompletionRequestor requestor) throws JavaModelException {
codeComplete(offset, requestor, DefaultWorkingCopyOwner.PRIMARY);
}
/**
* @see ICodeAssist#codeComplete(int, ICompletionRequestor, WorkingCopyOwner)
* @deprecated
*/
@Override
@Deprecated
public void codeComplete(int offset, ICompletionRequestor requestor, WorkingCopyOwner owner) throws JavaModelException {
if (requestor == null) {
throw new IllegalArgumentException("Completion requestor cannot be null"); //$NON-NLS-1$
}
codeComplete(offset, new org.eclipse.jdt.internal.codeassist.CompletionRequestorWrapper(requestor), owner);
}
@Override
public void codeComplete(int offset, CompletionRequestor requestor) throws JavaModelException {
codeComplete(offset, requestor, DefaultWorkingCopyOwner.PRIMARY);
}
@Override
public void codeComplete(int offset, CompletionRequestor requestor, IProgressMonitor monitor) throws JavaModelException {
codeComplete(offset, requestor, DefaultWorkingCopyOwner.PRIMARY, monitor);
}
@Override
public void codeComplete(int offset, CompletionRequestor requestor, WorkingCopyOwner owner) throws JavaModelException {
codeComplete(offset, requestor, owner, null);
}
@Override
public abstract void codeComplete(int offset, CompletionRequestor requestor, WorkingCopyOwner owner, IProgressMonitor monitor) throws JavaModelException;
/**
* @see ICodeAssist#codeSelect(int, int)
*/
@Override
public IJavaElement[] codeSelect(int offset, int length) throws JavaModelException {
return codeSelect(offset, length, DefaultWorkingCopyOwner.PRIMARY);
}
@Override
public abstract IJavaElement[] codeSelect(int offset, int length, WorkingCopyOwner owner) throws JavaModelException;
/**
* Returns a new element info for this element.
*/
@Override
protected Object createElementInfo() {
return new ClassFileInfo();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof AbstractClassFile)) return false;
AbstractClassFile other = (AbstractClassFile) o;
return this.name.equals(other.name) && this.parent.equals(other.parent);
}
/**
* Finds the deepest <code>IJavaElement</code> in the hierarchy of
* <code>elt</elt>'s children (including <code>elt</code> itself)
* which has a source range that encloses <code>position</code>
* according to <code>mapper</code>.
*/
protected IJavaElement findElement(IJavaElement elt, int position, SourceMapper mapper) {
SourceRange range = mapper.getSourceRange(elt);
if (range == null || position < range.getOffset() || range.getOffset() + range.getLength() - 1 < position) {
return null;
}
if (elt instanceof IParent) {
try {
IJavaElement[] children = ((IParent) elt).getChildren();
for (int i = 0; i < children.length; i++) {
IJavaElement match = findElement(children[i], position, mapper);
if (match != null) {
return match;
}
}
} catch (JavaModelException npe) {
// elt doesn't exist: return the element
}
}
return elt;
}
@Override
public byte[] getBytes() throws JavaModelException {
JavaElement pkg = (JavaElement) getParent();
if (pkg instanceof JarPackageFragment) {
JarPackageFragmentRoot root = (JarPackageFragmentRoot) pkg.getParent();
try {
String entryName = Util.concatWith(((PackageFragment) pkg).names, getElementName(), '/');
entryName = root.getClassFilePath(entryName);
return getClassFileContent(root, entryName);
// Java 9 - The below exception is not thrown in new scheme of things. Could cause issues?
// throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this));
} catch (IOException ioe) {
throw new JavaModelException(ioe, IJavaModelStatusConstants.IO_EXCEPTION);
} catch (CoreException e) {
if (e instanceof JavaModelException) {
throw (JavaModelException)e;
} else {
throw new JavaModelException(e);
}
}
} else {
IFile file = (IFile) resource();
return Util.getResourceContentsAsByteArray(file);
}
}
protected byte[] getClassFileContent(JarPackageFragmentRoot root, String className) throws CoreException, IOException {
byte[] contents = null;
String rootPath = root.getPath().toOSString();
if (org.eclipse.jdt.internal.compiler.util.Util.isJrt(rootPath)) {
try {
contents = org.eclipse.jdt.internal.compiler.util.JRTUtil.getClassfileContent(
new File(rootPath),
className,
root.getElementName());
} catch (ClassFormatException e) {
e.printStackTrace();
}
} else {
ZipFile zip = root.getJar();
try {
ZipEntry ze = zip.getEntry(className);
if (ze != null) {
contents = org.eclipse.jdt.internal.compiler.util.Util.getZipEntryByteContent(ze, zip);
}
} finally {
JavaModelManager.getJavaModelManager().closeZipFile(zip);
}
}
if (contents == null && Thread.interrupted()) // reading from JRT is interruptible
throw new OperationCanceledException();
return contents;
}
@Override
public IBuffer getBuffer() throws JavaModelException {
IStatus status = validateClassFile();
if (status.isOK()) {
return super.getBuffer();
} else {
switch (status.getCode()) {
case IJavaModelStatusConstants.ELEMENT_NOT_ON_CLASSPATH: // don't throw a JavaModelException to be able to open .class file outside the classpath (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=138507 )
case IJavaModelStatusConstants.INVALID_ELEMENT_TYPES: // don't throw a JavaModelException to be able to open .class file in proj==src case without source (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=221904 )
return null;
default:
throw new JavaModelException(status);
}
}
}
/**
* @see IMember#getTypeRoot()
*/
public ITypeRoot getTypeRoot() {
return this;
}
/**
* A class file has a corresponding resource unless it is contained
* in a jar.
*
* @see IJavaElement
*/
@Override
public IResource getCorrespondingResource() throws JavaModelException {
IPackageFragmentRoot root= (IPackageFragmentRoot)getParent().getParent();
if (root.isArchive()) {
return null;
} else {
return getUnderlyingResource();
}
}
public IJavaElement getElementAtConsideringSibling(int position) throws JavaModelException {
IPackageFragment fragment = (IPackageFragment)getParent();
PackageFragmentRoot root = (PackageFragmentRoot) fragment.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
SourceMapper mapper = root.getSourceMapper();
if (mapper == null) {
return null;
} else {
int index = this.name.indexOf('$');
int prefixLength = index < 0 ? this.name.length() : index;
IType type = null;
int start = -1;
int end = Integer.MAX_VALUE;
IJavaElement[] children = fragment.getChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof IOrdinaryClassFile) {
IOrdinaryClassFile classFile = (IOrdinaryClassFile) children[i];
String childName = classFile.getElementName();
int childIndex = childName.indexOf('$');
int childPrefixLength = childIndex < 0 ? childName.indexOf('.') : childIndex;
if (prefixLength == childPrefixLength && this.name.regionMatches(0, childName, 0, prefixLength)) {
// ensure this class file's buffer is open so that source ranges are computed
classFile.getBuffer();
SourceRange range = mapper.getSourceRange(classFile.getType());
if (range == SourceMapper.UNKNOWN_RANGE) continue;
int newStart = range.getOffset();
int newEnd = newStart + range.getLength() - 1;
if(newStart > start && newEnd < end
&& newStart <= position && newEnd >= position) {
type = classFile.getType();
start = newStart;
end = newEnd;
}
}
}
}
if(type != null) {
return findElement(type, position, mapper);
}
return null;
}
}
@Override
public String getElementName() {
return this.name + SuffixConstants.SUFFIX_STRING_class;
}
/**
* @see IJavaElement
*/
@Override
public int getElementType() {
return CLASS_FILE;
}
/*
* @see IJavaElement
*/
@Override
public IPath getPath() {
PackageFragmentRoot root = getPackageFragmentRoot();
if (root.isArchive()) {
return root.getPath();
} else {
return getParent().getPath().append(getElementName());
}
}
/*
* @see IJavaElement
*/
@Override
public IResource resource(PackageFragmentRoot root) {
return ((IContainer) ((Openable) this.parent).resource(root)).getFile(new Path(getElementName()));
}
/**
* @see ISourceReference
*/
@Override
public String getSource() throws JavaModelException {
IBuffer buffer = getBuffer();
if (buffer == null) {
return null;
}
return buffer.getContents();
}
/**
* @see ISourceReference
*/
@Override
public ISourceRange getSourceRange() throws JavaModelException {
IBuffer buffer = getBuffer();
if (buffer != null) {
String contents = buffer.getContents();
if (contents == null) return null;
return new SourceRange(0, contents.length());
} else {
return null;
}
}
/**
* @see IClassFile
* @deprecated
*/
@Override
@Deprecated
public IJavaElement getWorkingCopy(IProgressMonitor monitor, org.eclipse.jdt.core.IBufferFactory factory) throws JavaModelException {
return getWorkingCopy(BufferFactoryWrapper.create(factory), monitor);
}
/**
* @see Openable
*/
@Override
protected boolean hasBuffer() {
return true;
}
@Override
public int hashCode() {
return Util.combineHashCodes(this.name.hashCode(), this.parent.hashCode());
}
/**
* Returns true - class files are always read only.
*/
@Override
public boolean isReadOnly() {
return true;
}
private IStatus validateClassFile() {
IPackageFragmentRoot root = getPackageFragmentRoot();
try {
if (root.getKind() != IPackageFragmentRoot.K_BINARY)
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_ELEMENT_TYPES, root);
} catch (JavaModelException e) {
return e.getJavaModelStatus();
}
IJavaProject project = getJavaProject();
return JavaConventions.validateClassFileName(getElementName(), project.getOption(JavaCore.COMPILER_SOURCE, true), project.getOption(JavaCore.COMPILER_COMPLIANCE, true));
}
/**
* @see ICodeAssist#codeComplete(int, ICodeCompletionRequestor)
* @deprecated - should use codeComplete(int, ICompletionRequestor) instead
*/
@Override
@Deprecated
public void codeComplete(int offset, final org.eclipse.jdt.core.ICodeCompletionRequestor requestor) throws JavaModelException {
if (requestor == null){
codeComplete(offset, (ICompletionRequestor)null);
return;
}
codeComplete(
offset,
new ICompletionRequestor(){
@Override
public void acceptAnonymousType(char[] superTypePackageName,char[] superTypeName, char[][] parameterPackageNames,char[][] parameterTypeNames,char[][] parameterNames,char[] completionName,int modifiers,int completionStart,int completionEnd, int relevance) {
// ignore
}
@Override
public void acceptClass(char[] packageName, char[] className, char[] completionName, int modifiers, int completionStart, int completionEnd, int relevance) {
requestor.acceptClass(packageName, className, completionName, modifiers, completionStart, completionEnd);
}
@Override
public void acceptError(IProblem error) {
// was disabled in 1.0
}
@Override
public void acceptField(char[] declaringTypePackageName, char[] declaringTypeName, char[] fieldName, char[] typePackageName, char[] typeName, char[] completionName, int modifiers, int completionStart, int completionEnd, int relevance) {
requestor.acceptField(declaringTypePackageName, declaringTypeName, fieldName, typePackageName, typeName, completionName, modifiers, completionStart, completionEnd);
}
@Override
public void acceptInterface(char[] packageName,char[] interfaceName,char[] completionName,int modifiers,int completionStart,int completionEnd, int relevance) {
requestor.acceptInterface(packageName, interfaceName, completionName, modifiers, completionStart, completionEnd);
}
@Override
public void acceptKeyword(char[] keywordName,int completionStart,int completionEnd, int relevance){
requestor.acceptKeyword(keywordName, completionStart, completionEnd);
}
@Override
public void acceptLabel(char[] labelName,int completionStart,int completionEnd, int relevance){
requestor.acceptLabel(labelName, completionStart, completionEnd);
}
@Override
public void acceptLocalVariable(char[] localVarName,char[] typePackageName,char[] typeName,int modifiers,int completionStart,int completionEnd, int relevance){
// ignore
}
@Override
public void acceptMethod(char[] declaringTypePackageName,char[] declaringTypeName,char[] selector,char[][] parameterPackageNames,char[][] parameterTypeNames,char[][] parameterNames,char[] returnTypePackageName,char[] returnTypeName,char[] completionName,int modifiers,int completionStart,int completionEnd, int relevance){
// skip parameter names
requestor.acceptMethod(declaringTypePackageName, declaringTypeName, selector, parameterPackageNames, parameterTypeNames, returnTypePackageName, returnTypeName, completionName, modifiers, completionStart, completionEnd);
}
@Override
public void acceptMethodDeclaration(char[] declaringTypePackageName,char[] declaringTypeName,char[] selector,char[][] parameterPackageNames,char[][] parameterTypeNames,char[][] parameterNames,char[] returnTypePackageName,char[] returnTypeName,char[] completionName,int modifiers,int completionStart,int completionEnd, int relevance){
// ignore
}
@Override
public void acceptModifier(char[] modifierName,int completionStart,int completionEnd, int relevance){
requestor.acceptModifier(modifierName, completionStart, completionEnd);
}
@Override
public void acceptPackage(char[] packageName,char[] completionName,int completionStart,int completionEnd, int relevance){
requestor.acceptPackage(packageName, completionName, completionStart, completionEnd);
}
@Override
public void acceptType(char[] packageName,char[] typeName,char[] completionName,int completionStart,int completionEnd, int relevance){
requestor.acceptType(packageName, typeName, completionName, completionStart, completionEnd);
}
@Override
public void acceptVariableName(char[] typePackageName,char[] typeName,char[] varName,char[] completionName,int completionStart,int completionEnd, int relevance){
// ignore
}
});
}
@Override
protected IStatus validateExistence(IResource underlyingResource) {
// check whether the class file can be opened
IStatus status = validateClassFile();
if (!status.isOK())
return status;
if (underlyingResource != null) {
if (!underlyingResource.isAccessible())
return newDoesNotExistStatus();
PackageFragmentRoot root;
if ((underlyingResource instanceof IFolder) && (root = getPackageFragmentRoot()).isArchive()) { // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=204652
return root.newDoesNotExistStatus();
}
}
return JavaModelStatus.VERIFIED_OK;
}
@Override
public ISourceRange getNameRange() {
return null;
}
}