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

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2009, 2017 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
 *******************************************************************************/
package org.eclipse.jdt.internal.core;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaModelStatusConstants;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
import org.eclipse.jdt.internal.compiler.util.HashtableOfObjectToIntArray;
import org.eclipse.jdt.internal.core.util.Util;

public class JavadocContents {
    private static final int[] UNKNOWN_FORMAT = new int[0];

    private BinaryType type;
    private char[] content;

    private int childrenStart;

    private boolean hasComputedChildrenSections = false;
    private int indexOfFieldDetails;
    private int indexOfConstructorDetails;
    private int indexOfMethodDetails;
    private int indexOfEndOfClassData;

    private int indexOfFieldsBottom;
    private int indexOfAllMethodsTop;
    private int indexOfAllMethodsBottom;

    private int[] typeDocRange;
    private HashtableOfObjectToIntArray fieldDocRanges;
    private HashtableOfObjectToIntArray methodDocRanges;

    private int[] fieldAnchorIndexes;
    private int fieldAnchorIndexesCount;
    private int fieldLastAnchorFoundIndex;
    private int[] methodAnchorIndexes;
    private int methodAnchorIndexesCount;
    private int methodLastAnchorFoundIndex;
    private int[] unknownFormatAnchorIndexes;
    private int unknownFormatAnchorIndexesCount;
    private int unknownFormatLastAnchorFoundIndex;
    private int[] tempAnchorIndexes;
    private int tempAnchorIndexesCount;
    private int tempLastAnchorFoundIndex;

    public JavadocContents(BinaryType type, String content) {
        this(content);
        this.type = type;
    }

    public JavadocContents(String content) {
        this.content = content != null ? content.toCharArray() : null;
    }

    /*
     * Returns the part of the javadoc that describe the type
     */
    public String getTypeDoc() throws JavaModelException {
        if (this.content == null)
            return null;

        synchronized (this) {
            if (this.typeDocRange == null) {
                computeTypeRange();
            }
        }

        if (this.typeDocRange != null) {
            if (this.typeDocRange == UNKNOWN_FORMAT)
                throw new JavaModelException(
                        new JavaModelStatus(IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT, this.type));
            return String.valueOf(CharOperation.subarray(this.content, this.typeDocRange[0], this.typeDocRange[1]));
        }
        return null;
    }

    public String getPackageDoc() throws JavaModelException {
        if (this.content == null)
            return null;
        int[] range = null;
        int index = CharOperation.indexOf(JavadocConstants.PACKAGE_DESCRIPTION_START2, this.content, false, 0);
        if (index == -1) {
            index = CharOperation.indexOf(JavadocConstants.PACKAGE_DESCRIPTION_START, this.content, false, 0);
        }
        if (index != -1) {
            index = CharOperation.indexOf(JavadocConstants.ANCHOR_SUFFIX, this.content, false, index);
            if (index == -1)
                return null;

            int start = CharOperation.indexOf(JavadocConstants.H2_PREFIX, this.content, false, index);
            if (start != -1) {
                start = CharOperation.indexOf(JavadocConstants.H2_SUFFIX, this.content, false, start);
                if (start != -1)
                    index = start + JavadocConstants.H2_SUFFIX_LENGTH;
            }
        } else {
            index = CharOperation.indexOf(JavadocConstants.PACKAGE_DESCRIPTION_START3, this.content, false, 0);
        }
        if (index != -1) {
            int end = CharOperation.indexOf(JavadocConstants.BOTTOM_NAVBAR, this.content, false, index);
            if (end == -1)
                end = this.content.length - 1;
            range = new int[] { index, end };
            return String.valueOf(CharOperation.subarray(this.content, range[0], range[1]));
        }
        return null;
    }

    public String getModuleDoc() throws JavaModelException {
        if (this.content == null)
            return null;
        int index = CharOperation.indexOf(JavadocConstants.MODULE_DESCRIPTION_START, this.content, false, 0);
        if (index == -1)
            return null;
        int end = CharOperation.indexOf(JavadocConstants.BOTTOM_NAVBAR, this.content, false, index);
        if (end == -1)
            end = this.content.length - 1;
        return String.valueOf(CharOperation.subarray(this.content, index, end));
    }

    /*
     * Returns the part of the javadoc that describe a field of the type
     */
    public String getFieldDoc(IField child) throws JavaModelException {
        if (this.content == null)
            return null;

        int[] range = null;
        synchronized (this) {
            if (this.fieldDocRanges == null) {
                this.fieldDocRanges = new HashtableOfObjectToIntArray();
            } else {
                range = this.fieldDocRanges.get(child);
            }

            if (range == null) {
                range = computeFieldRange(child);
                this.fieldDocRanges.put(child, range);
            }
        }

        if (range != null) {
            if (range == UNKNOWN_FORMAT)
                throw new JavaModelException(
                        new JavaModelStatus(IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT, child));
            return String.valueOf(CharOperation.subarray(this.content, range[0], range[1]));
        }
        return null;
    }

    /*
     * Returns the part of the javadoc that describe a method of the type
     */
    public String getMethodDoc(IMethod child) throws JavaModelException {
        if (this.content == null)
            return null;

        int[] range = null;
        synchronized (this) {
            if (this.methodDocRanges == null) {
                this.methodDocRanges = new HashtableOfObjectToIntArray();
            } else {
                range = this.methodDocRanges.get(child);
            }

            if (range == null) {
                range = computeMethodRange(child);
                this.methodDocRanges.put(child, range);
            }
        }

        if (range != null) {
            if (range == UNKNOWN_FORMAT) {
                throw new JavaModelException(
                        new JavaModelStatus(IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT, child));
            }
            return String.valueOf(CharOperation.subarray(this.content, range[0], range[1]));
        }
        return null;
    }

    /*
     * Compute the ranges of the parts of the javadoc that describe each method of the type
     */
    private int[] computeChildRange(char[] anchor, int indexOfSectionBottom) throws JavaModelException {

        // checks each known anchor locations
        if (this.tempAnchorIndexesCount > 0) {
            for (int i = 0; i < this.tempAnchorIndexesCount; i++) {
                int anchorEndStart = this.tempAnchorIndexes[i];

                if (anchorEndStart != -1
                        && CharOperation.prefixEquals(anchor, this.content, false, anchorEndStart)) {

                    this.tempAnchorIndexes[i] = -1;

                    return computeChildRange(anchorEndStart, anchor, indexOfSectionBottom);
                }
            }
        }

        int fromIndex = this.tempLastAnchorFoundIndex;
        int[] index;

        // check each next unknown anchor locations
        index = getAnchorIndex(fromIndex);
        while (index[0] != -1 && (index[0] < indexOfSectionBottom || indexOfSectionBottom == -1)) {
            fromIndex = index[0] + 1;

            int anchorEndStart = index[0] + index[1];

            this.tempLastAnchorFoundIndex = anchorEndStart;

            if (CharOperation.prefixEquals(anchor, this.content, false, anchorEndStart)) {
                return computeChildRange(anchorEndStart, anchor, indexOfSectionBottom);
            } else {
                if (this.tempAnchorIndexes.length == this.tempAnchorIndexesCount) {
                    System.arraycopy(this.tempAnchorIndexes, 0,
                            this.tempAnchorIndexes = new int[this.tempAnchorIndexesCount + 20], 0,
                            this.tempAnchorIndexesCount);
                }

                this.tempAnchorIndexes[this.tempAnchorIndexesCount++] = anchorEndStart;
            }
            index = getAnchorIndex(fromIndex);
        }

        return null;
    }

    private int[] getAnchorIndex(int fromIndex) {
        int index = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, fromIndex);
        if (index != -1) {
            return new int[] { index, JavadocConstants.ANCHOR_PREFIX_START_LENGTH };
        }
        if (index == -1) {
            index = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START_2, this.content, false, fromIndex);
        }
        if (index == -1) {
            return new int[] { -1, -1 };
        } else {
            return new int[] { index, JavadocConstants.ANCHOR_PREFIX_START2_LENGTH };
        }
    }

    private int[] computeChildRange(int anchorEndStart, char[] anchor, int indexOfBottom) {
        int[] range = null;

        // try to find the bottom of the section
        if (indexOfBottom != -1) {
            // try to find the end of the anchor
            int indexOfEndLink = CharOperation.indexOf(JavadocConstants.ANCHOR_SUFFIX, this.content, false,
                    anchorEndStart + anchor.length);
            if (indexOfEndLink != -1) {
                // try to find the next anchor
                int indexOfNextElement = getAnchorIndex(indexOfEndLink)[0];

                int javadocStart = indexOfEndLink + JavadocConstants.ANCHOR_SUFFIX_LENGTH;
                int javadocEnd = indexOfNextElement == -1 ? indexOfBottom
                        : Math.min(indexOfNextElement, indexOfBottom);
                range = new int[] { javadocStart, javadocEnd };
            } else {
                // the anchor has no suffix
                range = UNKNOWN_FORMAT;
            }
        } else {
            // the detail section has no bottom
            range = UNKNOWN_FORMAT;
        }

        return range;
    }

    private void computeChildrenSections() {
        // try to find the next separator part
        int lastIndex = CharOperation.indexOf(JavadocConstants.SEPARATOR_START, this.content, false,
                this.childrenStart);
        lastIndex = lastIndex == -1 ? this.childrenStart : lastIndex;

        // try to find field detail start
        this.indexOfFieldDetails = CharOperation.indexOf(JavadocConstants.FIELD_DETAIL, this.content, false,
                lastIndex);
        lastIndex = this.indexOfFieldDetails == -1 ? lastIndex : this.indexOfFieldDetails;

        // try to find constructor detail start
        this.indexOfConstructorDetails = CharOperation.indexOf(JavadocConstants.CONSTRUCTOR_DETAIL, this.content,
                false, lastIndex);
        lastIndex = this.indexOfConstructorDetails == -1 ? lastIndex : this.indexOfConstructorDetails;

        // try to find method detail start
        this.indexOfMethodDetails = CharOperation.indexOf(JavadocConstants.METHOD_DETAIL, this.content, false,
                lastIndex);
        lastIndex = this.indexOfMethodDetails == -1 ? lastIndex : this.indexOfMethodDetails;

        // we take the end of class data
        this.indexOfEndOfClassData = CharOperation.indexOf(JavadocConstants.END_OF_CLASS_DATA, this.content, false,
                lastIndex);

        // try to find the field detail end
        this.indexOfFieldsBottom = this.indexOfConstructorDetails != -1 ? this.indexOfConstructorDetails
                : this.indexOfMethodDetails != -1 ? this.indexOfMethodDetails : this.indexOfEndOfClassData;

        this.indexOfAllMethodsTop = this.indexOfConstructorDetails != -1 ? this.indexOfConstructorDetails
                : this.indexOfMethodDetails;

        this.indexOfAllMethodsBottom = this.indexOfEndOfClassData;

        this.hasComputedChildrenSections = true;
    }

    /*
     * Compute the ranges of the parts of the javadoc that describe each child of the type (fields, methods)
     */
    private int[] computeFieldRange(IField field) throws JavaModelException {
        if (!this.hasComputedChildrenSections) {
            computeChildrenSections();
        }

        StringBuffer buffer = new StringBuffer(field.getElementName());
        buffer.append(JavadocConstants.ANCHOR_PREFIX_END);
        char[] anchor = String.valueOf(buffer).toCharArray();

        int[] range = null;

        if (this.indexOfFieldDetails == -1 || this.indexOfFieldsBottom == -1) {
            // the detail section has no top or bottom, so the doc has an unknown format
            if (this.unknownFormatAnchorIndexes == null) {
                this.unknownFormatAnchorIndexes = new int[this.type.getChildren().length];
                this.unknownFormatAnchorIndexesCount = 0;
                this.unknownFormatLastAnchorFoundIndex = this.childrenStart;
            }

            this.tempAnchorIndexes = this.unknownFormatAnchorIndexes;
            this.tempAnchorIndexesCount = this.unknownFormatAnchorIndexesCount;
            this.tempLastAnchorFoundIndex = this.unknownFormatLastAnchorFoundIndex;

            range = computeChildRange(anchor, this.indexOfFieldsBottom);

            this.unknownFormatLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
            this.unknownFormatAnchorIndexesCount = this.tempAnchorIndexesCount;
            this.unknownFormatAnchorIndexes = this.tempAnchorIndexes;
        } else {
            if (this.fieldAnchorIndexes == null) {
                this.fieldAnchorIndexes = new int[this.type.getFields().length];
                this.fieldAnchorIndexesCount = 0;
                this.fieldLastAnchorFoundIndex = this.indexOfFieldDetails;
            }

            this.tempAnchorIndexes = this.fieldAnchorIndexes;
            this.tempAnchorIndexesCount = this.fieldAnchorIndexesCount;
            this.tempLastAnchorFoundIndex = this.fieldLastAnchorFoundIndex;

            range = computeChildRange(anchor, this.indexOfFieldsBottom);

            this.fieldLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
            this.fieldAnchorIndexesCount = this.tempAnchorIndexesCount;
            this.fieldAnchorIndexes = this.tempAnchorIndexes;
        }

        return range;
    }

    /*
     * Compute the ranges of the parts of the javadoc that describe each method of the type
     */
    private int[] computeMethodRange(IMethod method) throws JavaModelException {
        if (!this.hasComputedChildrenSections) {
            computeChildrenSections();
        }

        char[] anchor = computeMethodAnchorPrefixEnd((BinaryMethod) method).toCharArray();

        int[] range = null;

        if (this.indexOfAllMethodsTop == -1 || this.indexOfAllMethodsBottom == -1) {
            // the detail section has no top or bottom, so the doc has an unknown format
            if (this.unknownFormatAnchorIndexes == null) {
                this.unknownFormatAnchorIndexes = new int[this.type.getChildren().length];
                this.unknownFormatAnchorIndexesCount = 0;
                this.unknownFormatLastAnchorFoundIndex = this.childrenStart;
            }

            this.tempAnchorIndexes = this.unknownFormatAnchorIndexes;
            this.tempAnchorIndexesCount = this.unknownFormatAnchorIndexesCount;
            this.tempLastAnchorFoundIndex = this.unknownFormatLastAnchorFoundIndex;

            range = computeChildRange(anchor, this.indexOfFieldsBottom);
            if (range == null) {
                range = computeChildRange(getJavadoc8Anchor(anchor), this.indexOfAllMethodsBottom);
            }

            this.unknownFormatLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
            this.unknownFormatAnchorIndexesCount = this.tempAnchorIndexesCount;
            this.unknownFormatAnchorIndexes = this.tempAnchorIndexes;
        } else {
            if (this.methodAnchorIndexes == null) {
                this.methodAnchorIndexes = new int[this.type.getFields().length];
                this.methodAnchorIndexesCount = 0;
                this.methodLastAnchorFoundIndex = this.indexOfAllMethodsTop;
            }

            this.tempAnchorIndexes = this.methodAnchorIndexes;
            this.tempAnchorIndexesCount = this.methodAnchorIndexesCount;
            this.tempLastAnchorFoundIndex = this.methodLastAnchorFoundIndex;

            range = computeChildRange(anchor, this.indexOfAllMethodsBottom);
            if (range == null) {
                range = computeChildRange(getJavadoc8Anchor(anchor), this.indexOfAllMethodsBottom);
            }

            this.methodLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
            this.methodAnchorIndexesCount = this.tempAnchorIndexesCount;
            this.methodAnchorIndexes = this.tempAnchorIndexes;
        }

        return range;
    }

    private static char[] getJavadoc8Anchor(char[] anchor) {
        // fix for bug 432284: [1.8] Javadoc-8-style anchors not found by IMethod#getAttachedJavadoc(..)
        char[] anchor8 = new char[anchor.length];
        int i8 = 0;
        for (int i = 0; i < anchor.length; i++) {
            char ch = anchor[i];
            switch (ch) {
            case '(':
            case ')':
            case ',':
                anchor8[i8++] = '-';
                break;
            case '[':
                anchor8[i8++] = ':';
                anchor8[i8++] = 'A';
                break;
            case ' ': // handled by preceding ','
            case ']': // handled by preceding '['
                break;
            default:
                anchor8[i8++] = ch;
            }
        }
        if (i8 != anchor.length) {
            anchor8 = CharOperation.subarray(anchor8, 0, i8);
        }
        return anchor8;
    }

    private String computeMethodAnchorPrefixEnd(BinaryMethod method) throws JavaModelException {
        String typeQualifiedName = null;
        if (this.type.isMember()) {
            IType currentType = this.type;
            StringBuffer buffer = new StringBuffer();
            while (currentType != null) {
                buffer.insert(0, currentType.getElementName());
                currentType = currentType.getDeclaringType();
                if (currentType != null) {
                    buffer.insert(0, '.');
                }
            }
            typeQualifiedName = buffer.toString();
        } else {
            typeQualifiedName = this.type.getElementName();
        }

        String methodName = method.getElementName();
        if (method.isConstructor()) {
            methodName = typeQualifiedName;
        }
        IBinaryMethod info = (IBinaryMethod) method.getElementInfo();

        char[] genericSignature = info.getGenericSignature();
        String anchor = null;
        if (genericSignature != null) {
            genericSignature = CharOperation.replaceOnCopy(genericSignature, '/', '.');
            anchor = Util.toAnchor(0, genericSignature, methodName, Flags.isVarargs(method.getFlags()));
            if (anchor == null)
                throw new JavaModelException(
                        new JavaModelStatus(IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT, method));
        } else {
            anchor = Signature.toString(method.getSignature().replace('/', '.'), methodName, null, true, false,
                    Flags.isVarargs(method.getFlags()));
        }
        IType declaringType = this.type;
        if (declaringType.isMember()) {
            // might need to remove a part of the signature corresponding to the synthetic argument (only for constructor)
            if (method.isConstructor() && !Flags.isStatic(declaringType.getFlags())) {
                int indexOfOpeningParen = anchor.indexOf('(');
                if (indexOfOpeningParen == -1) {
                    // should not happen as this is a method signature
                    return null;
                }
                int index = indexOfOpeningParen;
                indexOfOpeningParen++;
                int indexOfComma = anchor.indexOf(',', index);
                if (indexOfComma != -1) {
                    index = indexOfComma + 2;
                } else {
                    // no argument, but a synthetic argument
                    index = anchor.indexOf(')', index);
                }
                anchor = anchor.substring(0, indexOfOpeningParen) + anchor.substring(index);
            }
        }
        return anchor + JavadocConstants.ANCHOR_PREFIX_END;
    }

    /*
     * Compute the range of the part of the javadoc that describe the type
     */
    private void computeTypeRange() throws JavaModelException {
        final int indexOfStartOfClassData = CharOperation.indexOf(JavadocConstants.START_OF_CLASS_DATA,
                this.content, false);
        if (indexOfStartOfClassData == -1) {
            this.typeDocRange = UNKNOWN_FORMAT;
            return;
        }
        int indexOfNextSeparator = CharOperation.indexOf(JavadocConstants.SEPARATOR_START, this.content, false,
                indexOfStartOfClassData);
        if (indexOfNextSeparator == -1) {
            this.typeDocRange = UNKNOWN_FORMAT;
            return;
        }
        int indexOfNextSummary = CharOperation.indexOf(JavadocConstants.NESTED_CLASS_SUMMARY, this.content, false,
                indexOfNextSeparator);
        if (indexOfNextSummary == -1 && this.type.isEnum()) {
            // try to find enum constant summary start
            indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ENUM_CONSTANT_SUMMARY, this.content, false,
                    indexOfNextSeparator);
        }
        if (indexOfNextSummary == -1 && this.type.isAnnotation()) {
            // try to find required enum constant summary start
            indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ANNOTATION_TYPE_REQUIRED_MEMBER_SUMMARY,
                    this.content, false, indexOfNextSeparator);
            if (indexOfNextSummary == -1) {
                // try to find optional enum constant summary start
                indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ANNOTATION_TYPE_OPTIONAL_MEMBER_SUMMARY,
                        this.content, false, indexOfNextSeparator);
            }
        }
        if (indexOfNextSummary == -1) {
            // try to find field summary start
            indexOfNextSummary = CharOperation.indexOf(JavadocConstants.FIELD_SUMMARY, this.content, false,
                    indexOfNextSeparator);
        }
        if (indexOfNextSummary == -1) {
            // try to find constructor summary start
            indexOfNextSummary = CharOperation.indexOf(JavadocConstants.CONSTRUCTOR_SUMMARY, this.content, false,
                    indexOfNextSeparator);
        }
        if (indexOfNextSummary == -1) {
            // try to find method summary start
            indexOfNextSummary = CharOperation.indexOf(JavadocConstants.METHOD_SUMMARY, this.content, false,
                    indexOfNextSeparator);
        }

        if (indexOfNextSummary == -1) {
            // we take the end of class data
            indexOfNextSummary = CharOperation.indexOf(JavadocConstants.END_OF_CLASS_DATA, this.content, false,
                    indexOfNextSeparator);
        } else {
            // improve performance of computation of children ranges
            this.childrenStart = indexOfNextSummary + 1;
        }

        if (indexOfNextSummary == -1) {
            this.typeDocRange = UNKNOWN_FORMAT;
            return;
        }
        /*
         * Cut off the type hierarchy, see bug 119844.
         * We remove the contents between the start of class data and where
         * we guess the actual class comment starts.
         */
        int start = indexOfStartOfClassData + JavadocConstants.START_OF_CLASS_DATA_LENGTH;
        int indexOfFirstParagraph = CharOperation.indexOf(JavadocConstants.P.toCharArray(), this.content, false,
                start, indexOfNextSummary);
        int indexOfFirstDiv = CharOperation.indexOf(JavadocConstants.DIV_CLASS_BLOCK.toCharArray(), this.content,
                false, start, indexOfNextSummary);
        int afterHierarchy = indexOfNextSummary;
        if (indexOfFirstParagraph != -1 && indexOfFirstParagraph < afterHierarchy) {
            afterHierarchy = indexOfFirstParagraph;
        }
        if (indexOfFirstDiv != -1 && indexOfFirstDiv < afterHierarchy) {
            afterHierarchy = indexOfFirstDiv;
        }
        if (afterHierarchy != indexOfNextSummary) {
            start = afterHierarchy;
        }

        this.typeDocRange = new int[] { start, indexOfNextSummary };
    }
}