org.eclipse.jdt.internal.core.ClassFile.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.internal.core.ClassFile.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipFile;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
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.core.runtime.Status;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationDecorator;
import org.eclipse.jdt.internal.compiler.classfmt.ExternalAnnotationProvider;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.env.IDependent;
import org.eclipse.jdt.internal.compiler.env.IModule;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.nd.java.JavaNames;
import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor;
import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory;
import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
import org.eclipse.jdt.internal.core.util.MementoTokenizer;
import org.eclipse.jdt.internal.core.util.Util;

/**
 * @see IClassFile
 */

@SuppressWarnings({ "rawtypes" })
public class ClassFile extends AbstractClassFile implements IOrdinaryClassFile {

    protected BinaryType binaryType = null;

    private IPath externalAnnotationBase;

    /*
     * Creates a handle to a class file.
     */
    protected ClassFile(PackageFragment parent, String nameWithoutExtension) {
        super(parent, nameWithoutExtension);
    }

    /**
     * Creates the children elements for this class file adding the resulting
     * new handles and info objects to the newElements table. Returns true
     * if successful, or false if an error is encountered parsing the class file.
     *
     * @see Openable
     * @see Signature
     */
    @Override
    protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements,
            IResource underlyingResource) throws JavaModelException {
        IBinaryType typeInfo = getBinaryTypeInfo();
        if (typeInfo == null) {
            // The structure of a class file is unknown if a class file format errors occurred
            //during the creation of the diet class file representative of this ClassFile.
            info.setChildren(JavaElement.NO_ELEMENTS);
            return false;
        }

        // Make the type
        IType type = getType();
        info.setChildren(new IJavaElement[] { type });
        newElements.put(type, typeInfo);
        // Read children
        ((ClassFileInfo) info).readBinaryChildren(this, (HashMap) newElements, typeInfo);
        return true;
    }

    @Override
    public void codeComplete(int offset, CompletionRequestor requestor, WorkingCopyOwner owner,
            IProgressMonitor monitor) throws JavaModelException {
        String source = getSource();
        if (source != null) {
            BinaryType type = (BinaryType) getType();
            BasicCompilationUnit cu = new BasicCompilationUnit(getSource().toCharArray(), null,
                    type.sourceFileName((IBinaryType) type.getElementInfo()), getJavaProject()); // use project to retrieve corresponding .java IFile
            codeComplete(cu, cu, offset, requestor, owner, null/*extended context isn't computed*/, monitor);
        }
    }

    /**
     * @see ICodeAssist#codeSelect(int, int, WorkingCopyOwner)
     */
    @Override
    public IJavaElement[] codeSelect(int offset, int length, WorkingCopyOwner owner) throws JavaModelException {
        IBuffer buffer = getBuffer();
        char[] contents;
        if (buffer != null && (contents = buffer.getCharacters()) != null) {
            BinaryType type = (BinaryType) getType();
            BasicCompilationUnit cu = new BasicCompilationUnit(contents, null,
                    type.sourceFileName((IBinaryType) type.getElementInfo()), this);
            return super.codeSelect(cu, offset, length, owner);
        } else {
            //has no associated souce
            return new IJavaElement[] {};
        }
    }

    public boolean existsUsingJarTypeCache() {
        if (getPackageFragmentRoot().isArchive()) {
            JavaModelManager manager = JavaModelManager.getJavaModelManager();
            IType type = getType();
            Object info = manager.getInfo(type);
            if (info == JavaModelCache.NON_EXISTING_JAR_TYPE_INFO)
                return false;
            else if (info != null)
                return true;
            // info is null
            JavaElementInfo parentInfo = (JavaElementInfo) manager.getInfo(getParent());
            if (parentInfo != null) {
                // if parent is open, this class file must be in its children
                IJavaElement[] children = parentInfo.getChildren();
                for (int i = 0, length = children.length; i < length; i++) {
                    IJavaElement child = children[i];
                    if (child instanceof ClassFile && this.name.equals(((ClassFile) child).name))
                        return true;
                }
                return false;
            }
            try {
                info = getJarBinaryTypeInfo();
            } catch (CoreException | IOException | ClassFormatException e) {
                // leave info null
            }
            manager.putJarTypeInfo(type, info == null ? JavaModelCache.NON_EXISTING_JAR_TYPE_INFO : info);
            return info != null;
        } else
            return exists();
    }

    /**
     * @see ITypeRoot#findPrimaryType()
     */
    @Override
    public IType findPrimaryType() {
        IType primaryType = getType();
        if (primaryType.exists()) {
            return primaryType;
        }
        return null;
    }

    @Override
    public String getAttachedJavadoc(IProgressMonitor monitor) throws JavaModelException {
        return getType().getAttachedJavadoc(monitor);
    }

    /**
     * Returns the <code>ClassFileReader</code>specific for this IClassFile, based
     * on its underlying resource, or <code>null</code> if unable to create
     * the diet class file.
     * There are two cases to consider:<ul>
     * <li>a class file corresponding to an IFile resource</li>
     * <li>a class file corresponding to a zip entry in a JAR</li>
     * </ul>
     *
     * @exception JavaModelException when the IFile resource or JAR is not available
     * or when this class file is not present in the JAR
     */
    public IBinaryType getBinaryTypeInfo() throws JavaModelException {
        try {
            IBinaryType info = getJarBinaryTypeInfo();
            if (info == null) {
                throw newNotPresentException();
            }
            return info;
        } catch (ClassFormatException cfe) {
            //the structure remains unknown
            if (JavaCore.getPlugin().isDebugging()) {
                cfe.printStackTrace(System.err);
            }
            return null;
        } 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);
            }
        }
    }

    public String getName() {
        return this.name;
    }

    private IBinaryType getJarBinaryTypeInfo() throws CoreException, IOException, ClassFormatException {
        BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(this);

        if (descriptor == null) {
            return null;
        }
        IBinaryType result = null;
        IPackageFragmentRoot root = getPackageFragmentRoot();
        if (getPackageFragmentRoot() instanceof JarPackageFragmentRoot) {
            if (root instanceof JrtPackageFragmentRoot || this.name.equals(IModule.MODULE_INFO)) {
                PackageFragment pkg = (PackageFragment) getParent();
                JarPackageFragmentRoot jarRoot = (JarPackageFragmentRoot) getPackageFragmentRoot();
                String entryName = jarRoot.getClassFilePath(Util.concatWith(pkg.names, getElementName(), '/'));
                byte[] contents = getClassFileContent(jarRoot, entryName);
                if (contents != null) {
                    String fileName = root.getHandleIdentifier() + IDependent.JAR_FILE_ENTRY_SEPARATOR + entryName;
                    result = new ClassFileReader(contents, fileName.toCharArray(), false);
                }
            } else {
                result = BinaryTypeFactory.readType(descriptor, null);
            }
        } else {
            result = BinaryTypeFactory.readType(descriptor, null);
        }

        if (result == null) {
            return null;
        }

        // TODO(sxenos): setup the external annotation provider if the IBinaryType came from the index
        if (root.getKind() == IPackageFragmentRoot.K_BINARY) {
            JavaProject javaProject = (JavaProject) getAncestor(IJavaElement.JAVA_PROJECT);
            IClasspathEntry entry;
            try {
                entry = javaProject.getClasspathEntryFor(getPath());
            } catch (JavaModelException jme) {
                // Access via cached ClassFile/PF/PFR of a closed project?
                // Ignore and continue with result undecorated
                return result;
            }
            if (entry != null) {
                PackageFragment pkg = (PackageFragment) getParent();
                String entryName = Util.concatWith(pkg.names, getElementName(), '/');
                entryName = new String(
                        CharArrayUtils.concat(JavaNames.fieldDescriptorToBinaryName(descriptor.fieldDescriptor),
                                SuffixConstants.SUFFIX_CLASS));
                IProject project = javaProject.getProject();
                IPath externalAnnotationPath = ClasspathEntry.getExternalAnnotationPath(entry, project, false); // unresolved for use in ExternalAnnotationTracker
                if (externalAnnotationPath != null) {
                    result = setupExternalAnnotationProvider(project, externalAnnotationPath, result,
                            entryName.substring(0, entryName.length() - SuffixConstants.SUFFIX_CLASS.length));
                } else if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                    result = new ExternalAnnotationDecorator(result, true);
                }
            }
        }

        return result;
    }

    private IBinaryType setupExternalAnnotationProvider(IProject project, final IPath externalAnnotationPath,
            IBinaryType reader, final String typeName) {
        IBinaryType result = reader;
        // try resolve path within the workspace:
        IWorkspaceRoot root = project.getWorkspace().getRoot();
        IResource resource;
        if (externalAnnotationPath.segmentCount() == 1) {
            resource = root.getProject(externalAnnotationPath.lastSegment());
        } else {
            resource = root.getFolder(externalAnnotationPath);
            if (!resource.exists())
                resource = root.getFile(externalAnnotationPath);
        }
        String resolvedPath;
        if (resource.exists()) {
            if (resource.isVirtual()) {
                Util.log(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, "Virtual resource " + externalAnnotationPath //$NON-NLS-1$
                        + " cannot be used as annotationpath for project " + project.getName())); //$NON-NLS-1$
                return reader;
            }
            resolvedPath = resource.getLocation().toString(); // workspace lookup succeeded -> resolve it
        } else {
            resolvedPath = externalAnnotationPath.toString(); // not in workspace, use as is
        }
        ZipFile annotationZip = null;
        try {
            annotationZip = ExternalAnnotationDecorator.getAnnotationZipFile(resolvedPath,
                    new ExternalAnnotationDecorator.ZipFileProducer() {
                        @Override
                        public ZipFile produce() throws IOException {
                            try {
                                return JavaModelManager.getJavaModelManager().getZipFile(externalAnnotationPath); // use (absolute, but) unresolved path here
                            } catch (CoreException e) {
                                throw new IOException("Failed to read annotation file for " + typeName + " from " //$NON-NLS-1$//$NON-NLS-2$
                                        + externalAnnotationPath.toString(), e);
                            }
                        }
                    });

            ExternalAnnotationProvider annotationProvider = ExternalAnnotationDecorator
                    .externalAnnotationProvider(resolvedPath, typeName, annotationZip);
            result = new ExternalAnnotationDecorator(reader, annotationProvider);
        } catch (IOException e) {
            Util.log(e);
            return result;
        } finally {
            if (annotationZip != null)
                JavaModelManager.getJavaModelManager().closeZipFile(annotationZip);
        }
        if (annotationZip == null) {
            // Additional change listening for individual types only when annotations are in individual files.
            // Note that we also listen for classes that don't yet have an annotation file, to detect its creation
            this.externalAnnotationBase = externalAnnotationPath; // remember so we can unregister later
            ExternalAnnotationTracker.registerClassFile(externalAnnotationPath, new Path(typeName), this);
        }
        return result;
    }

    void closeAndRemoveFromJarTypeCache() throws JavaModelException {
        super.close();
        // triggered when external annotations have changed we need to recreate this class file
        JavaModelManager.getJavaModelManager().removeFromJarTypeCache(this.binaryType);
    }

    @Override
    public void close() throws JavaModelException {
        if (this.externalAnnotationBase != null) {
            String entryName = Util.concatWith(((PackageFragment) getParent()).names, this.name, '/');
            ExternalAnnotationTracker.unregisterClassFile(this.externalAnnotationBase, new Path(entryName));
        }
        super.close();
    }

    /**
     * @see IMember
     */
    @Override
    public IClassFile getClassFile() {
        return this;
    }

    /**
     * @see IClassFile
     */
    @Override
    public IJavaElement getElementAt(int position) throws JavaModelException {
        IJavaElement parentElement = getParent();
        while (parentElement.getElementType() != IJavaElement.PACKAGE_FRAGMENT_ROOT) {
            parentElement = parentElement.getParent();
        }
        PackageFragmentRoot root = (PackageFragmentRoot) parentElement;
        SourceMapper mapper = root.getSourceMapper();
        if (mapper == null) {
            return null;
        } else {
            // ensure this class file's buffer is open so that source ranges are computed
            getBuffer();

            IType type = getType();
            return findElement(type, position, mapper);
        }
    }

    /*
     * @see JavaElement
     */
    @Override
    public IJavaElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner) {
        switch (token.charAt(0)) {
        case JEM_TYPE:
            if (!memento.hasMoreTokens())
                return this;
            String typeName = memento.nextToken();
            JavaElement type = new BinaryType(this, typeName);
            return type.getHandleFromMemento(memento, owner);
        }
        return null;
    }

    /**
     * @see JavaElement#getHandleMemento()
     */
    @Override
    protected char getHandleMementoDelimiter() {
        return JavaElement.JEM_CLASSFILE;
    }

    /*
     * Returns the name of the toplevel type of this class file.
     */
    public String getTopLevelTypeName() {
        String topLevelTypeName = getElementName();
        int firstDollar = topLevelTypeName.indexOf('$');
        if (firstDollar != -1) {
            topLevelTypeName = topLevelTypeName.substring(0, firstDollar);
        } else {
            topLevelTypeName = topLevelTypeName.substring(0, topLevelTypeName.length() - SUFFIX_CLASS.length);
        }
        return topLevelTypeName;
    }

    /**
     * @see IClassFile
     */
    @Override
    public IType getType() {
        if (this.binaryType == null) {
            this.binaryType = new BinaryType(this, getTypeName());
        }
        return this.binaryType;
    }

    public String getTypeName() {
        // Internal class file name doesn't contain ".class" file extension
        int lastDollar = this.name.lastIndexOf('$');
        return lastDollar > -1 ? Util.localTypeName(this.name, lastDollar, this.name.length()) : this.name;
    }

    /*
     * @see IClassFile
     */
    @Override
    public ICompilationUnit getWorkingCopy(WorkingCopyOwner owner, IProgressMonitor monitor)
            throws JavaModelException {
        CompilationUnit workingCopy = new ClassFileWorkingCopy(this,
                owner == null ? DefaultWorkingCopyOwner.PRIMARY : owner);
        JavaModelManager manager = JavaModelManager.getJavaModelManager();
        JavaModelManager.PerWorkingCopyInfo perWorkingCopyInfo = manager.getPerWorkingCopyInfo(workingCopy,
                false/*don't create*/, true/*record usage*/, null/*not used since don't create*/);
        if (perWorkingCopyInfo != null) {
            return perWorkingCopyInfo.getWorkingCopy(); // return existing handle instead of the one created above
        }
        BecomeWorkingCopyOperation op = new BecomeWorkingCopyOperation(workingCopy, null);
        op.runOperation(monitor);
        return workingCopy;
    }

    /**
     * @see IClassFile
     */
    @Override
    public boolean isClass() throws JavaModelException {
        return getType().isClass();
    }

    /**
     * @see IClassFile
     */
    @Override
    public boolean isInterface() throws JavaModelException {
        return getType().isInterface();
    }

    /**
     * Opens and returns buffer on the source code associated with this class file.
     * Maps the source code to the children elements of this class file.
     * If no source code is associated with this class file,
     * <code>null</code> is returned.
     *
     * @see Openable
     */
    @Override
    protected IBuffer openBuffer(IProgressMonitor pm, Object info) throws JavaModelException {
        // Check the cache for the top-level type first
        IType outerMostEnclosingType = getOuterMostEnclosingType();
        IBuffer buffer = getBufferManager().getBuffer(outerMostEnclosingType.getClassFile());
        if (buffer == null) {
            SourceMapper mapper = getSourceMapper();
            IBinaryType typeInfo = info instanceof IBinaryType ? (IBinaryType) info : null;
            if (mapper != null) {
                buffer = mapSource(mapper, typeInfo, outerMostEnclosingType.getClassFile());
            }
        }
        return buffer;
    }

    /** Loads the buffer via SourceMapper, and maps it in SourceMapper */
    private IBuffer mapSource(SourceMapper mapper, IBinaryType info, IClassFile bufferOwner) {
        char[] contents = mapper.findSource(getType(), info);
        if (contents != null) {
            // create buffer
            IBuffer buffer = BufferManager.createBuffer(bufferOwner);
            if (buffer == null)
                return null;
            BufferManager bufManager = getBufferManager();
            bufManager.addBuffer(buffer);

            // set the buffer source
            if (buffer.getCharacters() == null) {
                buffer.setContents(contents);
            }

            // listen to buffer changes
            buffer.addBufferChangedListener(this);

            // do the source mapping
            mapper.mapSource((NamedMember) getOuterMostEnclosingType(), contents, info);

            return buffer;
        } else {
            // create buffer
            IBuffer buffer = BufferManager.createNullBuffer(bufferOwner);
            if (buffer == null)
                return null;
            BufferManager bufManager = getBufferManager();
            bufManager.addBuffer(buffer);

            // listen to buffer changes
            buffer.addBufferChangedListener(this);
            return buffer;
        }
    }

    /* package */ static String simpleName(char[] className) {
        if (className == null)
            return null;
        String simpleName = new String(unqualifiedName(className));
        int lastDollar = simpleName.lastIndexOf('$');
        if (lastDollar != -1)
            return Util.localTypeName(simpleName, lastDollar, simpleName.length());
        else
            return simpleName;
    }

    /** Returns the type of the top-level declaring class used to find the source code */
    private IType getOuterMostEnclosingType() {
        IType type = getType();
        IType enclosingType = type.getDeclaringType();
        while (enclosingType != null) {
            type = enclosingType;
            enclosingType = type.getDeclaringType();
        }
        return type;
    }

    /**
     * Returns the Java Model representation of the given name
     * which is provided in diet class file format, or <code>null</code>
     * if the given name is <code>null</code>.
     *
     * <p><code>ClassFileReader</code> format is similar to "java/lang/Object",
     * and corresponding Java Model format is "java.lang.Object".
     */

    public static char[] translatedName(char[] name) {
        if (name == null)
            return null;
        int nameLength = name.length;
        char[] newName = new char[nameLength];
        for (int i = 0; i < nameLength; i++) {
            if (name[i] == '/') {
                newName[i] = '.';
            } else {
                newName[i] = name[i];
            }
        }
        return newName;
    }

    /**
     * Returns the Java Model representation of the given names
     * which are provided in diet class file format, or <code>null</code>
     * if the given names are <code>null</code>.
     *
     * <p><code>ClassFileReader</code> format is similar to "java/lang/Object",
     * and corresponding Java Model format is "java.lang.Object".
     */

    /* package */ static char[][] translatedNames(char[][] names) {
        if (names == null)
            return null;
        int length = names.length;
        char[][] newNames = new char[length][];
        for (int i = 0; i < length; i++) {
            newNames[i] = translatedName(names[i]);
        }
        return newNames;
    }

    /**
     * Returns the Java Model format of the unqualified class name for the
     * given className which is provided in diet class file format,
     * or <code>null</code> if the given className is <code>null</code>.
     * (This removes the package name, but not enclosing type names).
     *
     * <p><code>ClassFileReader</code> format is similar to "java/lang/Object",
     * and corresponding Java Model simple name format is "Object".
     */

    /* package */ static char[] unqualifiedName(char[] className) {
        if (className == null)
            return null;
        int count = 0;
        for (int i = className.length - 1; i > -1; i--) {
            if (className[i] == '/') {
                char[] name = new char[count];
                System.arraycopy(className, i + 1, name, 0, count);
                return name;
            }
            count++;
        }
        return className;
    }
}