Java tutorial
/******************************************************************************* * 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 }; } }