/*******************************************************************************
* Copyright (c) 2009 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.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.IJavaProject;
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.type= type;
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;
}
/*
* 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.indexOf(anchor, this.content, false, anchorEndStart) == anchorEndStart) {
this.tempAnchorIndexes[i]= -1;
return computeChildRange(anchorEndStart, anchor, indexOfSectionBottom);
}
}
}
int fromIndex= this.tempLastAnchorFoundIndex;
int index;
// check each next unknown anchor locations
while ((index= CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, fromIndex)) != -1 && (index < indexOfSectionBottom || indexOfSectionBottom == -1)) {
fromIndex= index + 1;
int anchorEndStart= index + JavadocConstants.ANCHOR_PREFIX_START_LENGHT;
this.tempLastAnchorFoundIndex= anchorEndStart;
if (CharOperation.indexOf(anchor, this.content, false, anchorEndStart) == 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;
}
}
return null;
}
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= CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, indexOfEndLink);
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);
// 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);
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);
this.methodLastAnchorFoundIndex= this.tempLastAnchorFoundIndex;
this.methodAnchorIndexesCount= this.tempAnchorIndexesCount;
this.methodAnchorIndexes= this.tempAnchorIndexes;
}
return range;
}
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= new String(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()) {
int depth= 0;
final String packageFragmentName= declaringType.getPackageFragment().getElementName();
// might need to remove a part of the signature corresponding to the synthetic argument
final IJavaProject javaProject= declaringType.getJavaProject();
char[][] typeNames= CharOperation.splitOn('.', typeQualifiedName.toCharArray());
if (!Flags.isStatic(declaringType.getFlags()))
depth++;
StringBuffer typeName= new StringBuffer();
for (int i= 0, max= typeNames.length; i < max; i++) {
if (typeName.length() == 0) {
typeName.append(typeNames[i]);
} else {
typeName.append('.').append(typeNames[i]);
}
IType resolvedType= javaProject.findType(packageFragmentName, String.valueOf(typeName));
if (resolvedType != null && resolvedType.isMember() && !Flags.isStatic(resolvedType.getFlags()))
depth++;
}
if (depth != 0) {
int indexOfOpeningParen= anchor.indexOf('(');
if (indexOfOpeningParen == -1)
return null;
int index= indexOfOpeningParen;
indexOfOpeningParen++;
for (int i= 0; i < depth; i++) {
int indexOfComma= anchor.indexOf(',', index);
if (indexOfComma != -1) {
index= indexOfComma + 2;
}
}
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;
}
/*
* Check out to cut off the hierarchy see 119844
* We remove what the contents between the start of class data and the first <P>
*/
int start= indexOfStartOfClassData + JavadocConstants.START_OF_CLASS_DATA_LENGTH;
int indexOfFirstParagraph= CharOperation.indexOf("<P>".toCharArray(), this.content, false, start); //$NON-NLS-1$
if (indexOfFirstParagraph != -1 && indexOfFirstParagraph < indexOfNextSummary) {
start= indexOfFirstParagraph;
}
this.typeDocRange= new int[] { start, indexOfNextSummary };
}
}