org.eclipse.che.jdt.javadoc.JavaElementLinks.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.jdt.javadoc.JavaElementLinks.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2014 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
 *******************************************************************************/
package org.eclipse.che.jdt.javadoc;

import org.eclipse.jdt.core.JavaCore;
import org.eclipse.che.jdt.util.JavaModelUtil;
import org.eclipse.jdt.internal.core.JavaProject;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;

/**
 * Links inside Javadoc hovers and Javadoc view.
 *
 * @since 3.4
 */
public class JavaElementLinks {

    private static final Logger LOG = LoggerFactory.getLogger(JavaElementLinks.class);
    public static final char LINK_BRACKET_REPLACEMENT = '\u2603';
    /**
     * The link is composed of a number of segments, separated by LINK_SEPARATOR:
     * <p>
     * segments[0]: ""<br>
     * segments[1]: baseElementHandle<br>
     * segments[2]: typeName<br>
     * segments[3]: memberName<br>
     * segments[4...]: parameterTypeName (optional)
     */
    public static final char LINK_SEPARATOR = '\u2602';

    /**
     * Creates an {@link java.net.URI} with the given scheme for the given element.
     *
     * @param scheme the scheme
     * @param element the element
     * @return an {@link java.net.URI}, encoded as {@link java.net.URI#toASCIIString() ASCII} string, ready to be used
     *         as <code>href</code> attribute in an <code>&lt;a&gt;</code> tag
     * @throws java.net.URISyntaxException if the arguments were invalid
     */
    public static String createURI(String scheme, IJavaElement element) throws URISyntaxException {
        return createURI(scheme, element, null, null, null);
    }

    /**
     * Creates an {@link java.net.URI} with the given scheme based on the given element.
     * The additional arguments specify a member referenced from the given element.
     *
     * @param scheme a scheme
     * @param element the declaring element
     * @param refTypeName a (possibly qualified) type or package name, can be <code>null</code>
     * @param refMemberName a member name, can be <code>null</code>
     * @param refParameterTypes a (possibly empty) array of (possibly qualified) parameter type
     *            names, can be <code>null</code>
     * @return an {@link java.net.URI}, encoded as {@link java.net.URI#toASCIIString() ASCII} string, ready to be used
     *         as <code>href</code> attribute in an <code>&lt;a&gt;</code> tag
     * @throws URISyntaxException if the arguments were invalid
     */
    public static String createURI(String scheme, IJavaElement element, String refTypeName, String refMemberName,
            String[] refParameterTypes) throws URISyntaxException {
        /*
         * We use an opaque URI, not ssp and fragments (to work around Safari bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=212527 (wrongly encodes #)).
         */

        StringBuilder ssp = new StringBuilder(60);
        //        ssp.append(scheme);
        ssp.append(LINK_SEPARATOR); // make sure first character is not a / (would be hierarchical URI)

        // replace '[' manually, since URI confuses it for an IPv6 address as per RFC 2732:
        ssp.append(element.getHandleIdentifier().replace('[', LINK_BRACKET_REPLACEMENT)); // segments[1]

        if (refTypeName != null) {
            ssp.append(LINK_SEPARATOR);
            ssp.append(refTypeName); // segments[2]

            if (refMemberName != null) {
                ssp.append(LINK_SEPARATOR);
                ssp.append(refMemberName); // segments[3]

                if (refParameterTypes != null) {
                    ssp.append(LINK_SEPARATOR);
                    for (int i = 0; i < refParameterTypes.length; i++) {
                        ssp.append(refParameterTypes[i]); // segments[4|5|..]
                        if (i != refParameterTypes.length - 1) {
                            ssp.append(LINK_SEPARATOR);
                        }
                    }
                }
            }
        }
        try {
            String encode = URLEncoder.encode(ssp.toString(), "UTF-8");
            return new URI(scheme + encode).toASCIIString();
        } catch (UnsupportedEncodingException e) {
            throw new URISyntaxException(ssp.toString(), e.getMessage());
        }
    }

    /**
     * Creates a link with the given URI and label text.
     *
     * @param uri the URI
     * @param label the label
     * @return the HTML link
     * @since 3.6
     */
    public static String createLink(String uri, String label) {
        return "<a href='" + uri + "'>" + label + "</a>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    }

    /**
     * Returns the label for a Java element with the flags as defined by {@link JavaElementLabels}.
     * Referenced element names in the label (except the given element's name) are rendered as
     * header links.
     *
     * @param element the element to render
     * @param flags the rendering flags
     * @return the label of the Java element
     * @since 3.5
     */
    public static String getElementLabel(IJavaElement element, long flags) {
        return getElementLabel(element, flags, false);
    }

    /**
     * Returns the label for a Java element with the flags as defined by {@link JavaElementLabels}.
     * Referenced element names in the label are rendered as header links.
     * If <code>linkAllNames</code> is <code>false</code>, don't link the name of the given element
     *
     * @param element the element to render
     * @param flags the rendering flags
     * @param linkAllNames if <code>true</code>, link all names; if <code>false</code>, link all names except original element's name
     * @return the label of the Java element
     * @since 3.6
     */
    public static String getElementLabel(IJavaElement element, long flags, boolean linkAllNames) {
        StringBuffer buf = new StringBuffer();

        //        if (!Strings.USE_TEXT_PROCESSOR) {
        //            new JavaElementLinkedLabelComposer(linkAllNames ? null : element, buf).appendElementLabel(element, flags);
        //            return Strings.markJavaElementLabelLTR(buf.toString());
        //        } else {
        String label = JavaElementLabels.getElementLabel(element, flags);
        return label.replaceAll("<", "&lt;").replaceAll(">", "&gt;"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
        //        }
    }

    public static IJavaElement parseURI(String ssp, JavaProject project) {
        //        String ssp= uri.getSchemeSpecificPart();
        String[] segments = ssp.split(String.valueOf(LINK_SEPARATOR));

        // replace '[' manually, since URI confuses it for an IPv6 address as per RFC 2732:
        IJavaElement element = JavaCore.create(segments[1].replace(LINK_BRACKET_REPLACEMENT, '['));

        if (segments.length > 2) {
            String refTypeName = segments[2];
            if (refTypeName.indexOf('.') == -1) {
                try {
                    ITypeParameter resolvedTypeVariable = resolveTypeVariable(element, refTypeName);
                    if (resolvedTypeVariable != null)
                        return resolvedTypeVariable;
                } catch (JavaModelException e) {
                    LOG.error(e.getMessage(), e);
                }
            }
            if (element instanceof IAnnotation) {
                element = element.getParent();
            }

            if (element instanceof ILocalVariable) {
                element = ((ILocalVariable) element).getDeclaringMember();
            } else if (element instanceof ITypeParameter) {
                element = ((ITypeParameter) element).getDeclaringMember();
            }
            if (element instanceof IMember && !(element instanceof IType)) {
                element = ((IMember) element).getDeclaringType();
            }

            if (element instanceof IPackageFragment) {
                try {
                    IPackageFragment root = (IPackageFragment) element;
                    element = resolvePackageInfoType(root, refTypeName);
                    if (element == null) {
                        // find it as package
                        IJavaProject javaProject = root.getJavaProject();
                        return JavaModelUtil.findTypeContainer(javaProject, refTypeName);
                    }
                } catch (JavaModelException e) {
                    LOG.error(e.getMessage(), e);
                }
            }

            if (element instanceof IType) {
                try {
                    IType type = (IType) element;
                    if (refTypeName.length() > 0) {
                        type = resolveType(type, refTypeName);
                        if (type == null) {
                            IPackageFragment pack = JavaModelUtil.getPackageFragmentRoot(element)
                                    .getPackageFragment(refTypeName);
                            if (pack.exists())
                                return pack;
                        }
                    }
                    if (type != null) {
                        element = type;
                        if (segments.length > 3) {
                            String refMemberName = segments[3];
                            if (segments.length > 4) {
                                String[] paramSignatures = new String[segments[4].length() == 0 ? 0
                                        : segments.length - 4];
                                for (int i = 0; i < paramSignatures.length; i++) {
                                    paramSignatures[i] = Signature.createTypeSignature(segments[i + 4], false);
                                }
                                IMethod method = type.getMethod(refMemberName, paramSignatures);
                                IMethod[] methods = type.findMethods(method);
                                if (methods != null) {
                                    return methods[0];
                                } else {
                                    //TODO: methods whose signature contains type parameters can not be found
                                    // easily, since the Javadoc references are erasures

                                    //Shortcut: only check name and parameter count:
                                    methods = type.getMethods();
                                    for (int i = 0; i < methods.length; i++) {
                                        method = methods[i];
                                        if (method.getElementName().equals(refMemberName)
                                                && method.getNumberOfParameters() == paramSignatures.length)
                                            return method;
                                    }

                                    //                                    // reference can also point to method from supertype:
                                    //                                    ITypeHierarchy hierarchy= SuperTypeHierarchyCache.getTypeHierarchy(type);
                                    //                                    method= JavaModelUtil.findMethodInHierarchy(hierarchy, type, refMemberName, paramSignatures, false);
                                    //                                    if (method != null)
                                    //                                        return method;
                                }
                            } else {
                                IField field = type.getField(refMemberName);
                                if (field.exists()) {
                                    return field;
                                } else {
                                    IMethod[] methods = type.getMethods();
                                    for (int i = 0; i < methods.length; i++) {
                                        IMethod method = methods[i];
                                        if (method.getElementName().equals(refMemberName))
                                            return method;
                                    }
                                }
                            }

                        }
                    } else {
                        // FIXME: either remove or show dialog
                        //                  JavaPlugin.logErrorMessage("JavaElementLinks could not resolve " + uri); //$NON-NLS-1$
                    }
                    return type;
                } catch (JavaModelException e) {
                    LOG.error(e.getMessage(), e);
                }
            }
        }
        return element;
    }

    private static IType resolvePackageInfoType(IPackageFragment pack, String refTypeName)
            throws JavaModelException {
        // Note: The scoping rules of JLS7 6.3 are broken for package-info.java, see https://bugs.eclipse.org/216451#c4
        // We follow the javadoc tool's implementation and only support fully-qualified type references:
        IJavaProject javaProject = pack.getJavaProject();
        return javaProject.findType(refTypeName, (IProgressMonitor) null);

        // This implementation would make sense, but the javadoc tool doesn't support it:
        //      IClassFile classFile= pack.getClassFile(JavaModelUtil.PACKAGE_INFO_CLASS);
        //      if (classFile.exists()) {
        //         return resolveType(classFile.getType(), refTypeName);
        //      }
        //
        //      // check if refTypeName is a qualified name
        //      int firstDot= refTypeName.indexOf('.');
        //      if (firstDot != -1) {
        //         String typeNameRest= refTypeName.substring(firstDot + 1);
        //         String simpleTypeName= refTypeName.substring(0, firstDot);
        //         IType simpleType= resolvePackageInfoType(pack, simpleTypeName);
        //         if (simpleType != null) {
        //            // a type-qualified name
        //            return resolveType(simpleType, typeNameRest);
        //         } else {
        //            // a fully-qualified name
        //            return javaProject.findType(refTypeName, (IProgressMonitor) null);
        //         }
        //      }
        //
        //      ICompilationUnit cu= pack.getCompilationUnit(JavaModelUtil.PACKAGE_INFO_JAVA);
        //      if (! cu.exists()) {
        //         // refTypeName is a simple name in the package-info.java from the source attachment. Sorry, we give up here...
        //         return null;
        //      }
        //
        //      // refTypeName is a simple name in a CU. Let's play the shadowing rules of JLS7 6.4.1:
        //      // 1) single-type import
        //      // 2) enclosing package
        //      // 3) java.lang.* (JLS7 7.3)
        //      // 4) on-demand import
        //      IImportDeclaration[] imports= cu.getImports();
        //      for (int i= 0; i < imports.length; i++) {
        //         IImportDeclaration importDecl= imports[i];
        //         String name= importDecl.getElementName();
        //         if (Flags.isStatic(importDecl.getFlags())) {
        //            imports[i]= null;
        //         } else    if (! importDecl.isOnDemand()) {
        //            if (name.endsWith('.' + refTypeName)) {
        //               // 1) single-type import
        //               IType type= javaProject.findType(name, (IProgressMonitor) null);
        //               if (type != null)
        //                  return type;
        //            }
        //            imports[i]= null;
        //         }
        //      }
        //
        //      // 2) enclosing package
        //      IType type= javaProject.findType(pack.getElementName() + '.' + refTypeName, (IProgressMonitor) null);
        //      if (type != null)
        //         return type;
        //
        //      // 3) java.lang.* (JLS7 7.3)
        //      type= javaProject.findType("java.lang." + refTypeName, (IProgressMonitor) null); //$NON-NLS-1$
        //      if (type != null)
        //         return type;
        //
        //      // 4) on-demand import
        //      for (int i= 0; i < imports.length; i++) {
        //         IImportDeclaration importDecl= imports[i];
        //         if (importDecl != null) {
        //            String name= importDecl.getElementName();
        //            name= name.substring(0, name.length() - 1); //remove the *
        //            type= javaProject.findType(name + refTypeName, (IProgressMonitor) null);
        //            if (type != null)
        //               return type;
        //         }
        //      }
        //      return null;
    }

    private static ITypeParameter resolveTypeVariable(IJavaElement baseElement, String typeVariableName)
            throws JavaModelException {
        while (baseElement != null) {
            switch (baseElement.getElementType()) {
            case IJavaElement.METHOD:
                IMethod method = (IMethod) baseElement;
                ITypeParameter[] typeParameters = method.getTypeParameters();
                for (int i = 0; i < typeParameters.length; i++) {
                    ITypeParameter typeParameter = typeParameters[i];
                    if (typeParameter.getElementName().equals(typeVariableName)) {
                        return typeParameter;
                    }
                }
                break;

            case IJavaElement.TYPE:
                IType type = (IType) baseElement;
                typeParameters = type.getTypeParameters();
                for (int i = 0; i < typeParameters.length; i++) {
                    ITypeParameter typeParameter = typeParameters[i];
                    if (typeParameter.getElementName().equals(typeVariableName)) {
                        return typeParameter;
                    }
                }
                break;

            case IJavaElement.JAVA_MODEL:
            case IJavaElement.JAVA_PROJECT:
            case IJavaElement.PACKAGE_FRAGMENT:
            case IJavaElement.PACKAGE_FRAGMENT_ROOT:

            case IJavaElement.CLASS_FILE:
            case IJavaElement.COMPILATION_UNIT:

            case IJavaElement.PACKAGE_DECLARATION:
            case IJavaElement.IMPORT_CONTAINER:
            case IJavaElement.IMPORT_DECLARATION:
                return null;

            default:
                break;
            }
            // look for type parameters in enclosing members:
            baseElement = baseElement.getParent();
        }
        return null;
    }

    private static IType resolveType(IType baseType, String refTypeName) throws JavaModelException {
        if (refTypeName.length() == 0)
            return baseType;

        String[][] resolvedNames = baseType.resolveType(refTypeName);
        if (resolvedNames != null && resolvedNames.length > 0) {
            return baseType.getJavaProject().findType(resolvedNames[0][0], resolvedNames[0][1].replace('$', '.'),
                    (IProgressMonitor) null);

        } else if (baseType.isBinary()) {
            // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=206597
            IType type = baseType.getJavaProject().findType(refTypeName, (IProgressMonitor) null);
            if (type == null) {
                // could be unqualified reference:
                type = baseType.getJavaProject().findType(
                        baseType.getPackageFragment().getElementName() + '.' + refTypeName,
                        (IProgressMonitor) null);
            }
            return type;

        } else {
            return null;
        }
    }
}