/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javadoc.main;
import java.io.File;
import java.util.Locale;
import com.sun.javadoc.*;
import com.sun.tools.javac.code.Printer;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type.CapturedType;
import com.sun.tools.javac.util.*;
import static com.sun.tools.javac.code.Kinds.Kind.*;
Represents a see also documentation tag.
The @see tag can be plain text, or reference a class or member.
This is NOT part of any supported API.
If you write code that depends on this, you do so at your own risk.
This code and its internal interfaces are subject to change or
deletion without notice.
Author: Kaiyang Liu (original), Robert Field (rewrite), Atul M Dambalkar
/**
* Represents a see also documentation tag.
* The @see tag can be plain text, or reference a class or member.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*
* @author Kaiyang Liu (original)
* @author Robert Field (rewrite)
* @author Atul M Dambalkar
*
*/
@Deprecated(since="9", forRemoval=true)
@SuppressWarnings("removal")
class SeeTagImpl extends TagImpl implements SeeTag, LayoutCharacters {
//### TODO: Searching for classes, fields, and methods
//### should follow the normal rules applied by the compiler.
where of where#what - i.e. the class name (may be empty)
/**
* where of where#what - i.e. the class name (may be empty)
*/
private String where;
what of where#what - i.e. the member (may be null)
/**
* what of where#what - i.e. the member (may be null)
*/
private String what;
private PackageDoc referencedPackage;
private ClassDoc referencedClass;
private MemberDoc referencedMember;
String label = "";
SeeTagImpl(DocImpl holder, String name, String text) {
super(holder, name, text);
parseSeeString();
if (where != null) {
ClassDocImpl container = null;
if (holder instanceof MemberDoc) {
container =
(ClassDocImpl)((ProgramElementDoc)holder).containingClass();
} else if (holder instanceof ClassDoc) {
container = (ClassDocImpl)holder;
}
findReferenced(container);
if (showRef) showRef();
}
}
private static final boolean showRef = false;
private void showRef() {
Symbol sym;
if (referencedMember != null) {
if (referencedMember instanceof MethodDocImpl)
sym = ((MethodDocImpl) referencedMember).sym;
else if (referencedMember instanceof FieldDocImpl)
sym = ((FieldDocImpl) referencedMember).sym;
else
sym = ((ConstructorDocImpl) referencedMember).sym;
} else if (referencedClass != null) {
sym = ((ClassDocImpl) referencedClass).tsym;
} else if (referencedPackage != null) {
sym = ((PackageDocImpl) referencedPackage).sym;
} else
return;
final JavacMessages messages = JavacMessages.instance(docenv().context);
Locale locale = Locale.getDefault();
Printer printer = new Printer() {
int count;
@Override
protected String localize(Locale locale, String key, Object... args) {
return messages.getLocalizedString(locale, key, args);
}
@Override
protected String capturedVarId(CapturedType t, Locale locale) {
return "CAP#" + (++count);
}
};
String s = text.replaceAll("\\s+", " "); // normalize white space
int sp = s.indexOf(" ");
int lparen = s.indexOf("(");
int rparen = s.indexOf(")");
String seetext = (sp == -1) ? s
: (lparen == -1 || sp < lparen) ? s.substring(0, sp)
: s.substring(0, rparen + 1);
File file = new File(holder.position().file().getAbsoluteFile().toURI().normalize());
StringBuilder sb = new StringBuilder();
sb.append("+++ ").append(file).append(": ")
.append(name()).append(" ").append(seetext).append(": ");
sb.append(sym.getKind()).append(" ");
if (sym.kind == MTH || sym.kind == VAR)
sb.append(printer.visit(sym.owner, locale)).append(".");
sb.append(printer.visit(sym, locale));
System.err.println(sb);
}
get the class name part of @see, For instance,
if the comment is @see String#startsWith(java.lang.String) .
This function returns String.
Returns null if format was not that of java reference.
Return empty string if class name was not specified..
/**
* get the class name part of @see, For instance,
* if the comment is @see String#startsWith(java.lang.String) .
* This function returns String.
* Returns null if format was not that of java reference.
* Return empty string if class name was not specified..
*/
public String referencedClassName() {
return where;
}
get the package referenced by @see. For instance,
if the comment is @see java.lang
This function returns a PackageDocImpl for java.lang
Returns null if no known package found.
/**
* get the package referenced by @see. For instance,
* if the comment is @see java.lang
* This function returns a PackageDocImpl for java.lang
* Returns null if no known package found.
*/
public PackageDoc referencedPackage() {
return referencedPackage;
}
get the class referenced by the class name part of @see, For instance,
if the comment is @see String#startsWith(java.lang.String) .
This function returns a ClassDocImpl for java.lang.String.
Returns null if class is not a class specified on the javadoc command line..
/**
* get the class referenced by the class name part of @see, For instance,
* if the comment is @see String#startsWith(java.lang.String) .
* This function returns a ClassDocImpl for java.lang.String.
* Returns null if class is not a class specified on the javadoc command line..
*/
public ClassDoc referencedClass() {
return referencedClass;
}
get the name of the member referenced by the prototype part of @see,
For instance,
if the comment is @see String#startsWith(java.lang.String) .
This function returns "startsWith(java.lang.String)"
Returns null if format was not that of java reference.
Return empty string if member name was not specified..
/**
* get the name of the member referenced by the prototype part of @see,
* For instance,
* if the comment is @see String#startsWith(java.lang.String) .
* This function returns "startsWith(java.lang.String)"
* Returns null if format was not that of java reference.
* Return empty string if member name was not specified..
*/
public String referencedMemberName() {
return what;
}
get the member referenced by the prototype part of @see,
For instance,
if the comment is @see String#startsWith(java.lang.String) .
This function returns a MethodDocImpl for startsWith.
Returns null if member could not be determined.
/**
* get the member referenced by the prototype part of @see,
* For instance,
* if the comment is @see String#startsWith(java.lang.String) .
* This function returns a MethodDocImpl for startsWith.
* Returns null if member could not be determined.
*/
public MemberDoc referencedMember() {
return referencedMember;
}
parse @see part of comment. Determine 'where' and 'what'
/**
* parse @see part of comment. Determine 'where' and 'what'
*/
private void parseSeeString() {
int len = text.length();
if (len == 0) {
return;
}
switch (text.charAt(0)) {
case '<':
if (text.charAt(len-1) != '>') {
docenv().warning(holder,
"tag.see.no_close_bracket_on_url",
name, text);
}
return;
case '"':
if (len == 1 || text.charAt(len-1) != '"') {
docenv().warning(holder,
"tag.see.no_close_quote",
name, text);
} else {
// text = text.substring(1,len-1); // strip quotes
}
return;
}
// check that the text is one word, with possible parentheses
// this part of code doesn't allow
// @see <a href=.....>asfd</a>
// comment it.
// the code assumes that there is no initial white space.
int parens = 0;
int commentstart = 0;
int start = 0;
int cp;
for (int i = start; i < len ; i += Character.charCount(cp)) {
cp = text.codePointAt(i);
switch (cp) {
case '(': parens++; break;
case ')': parens--; break;
case '[': case ']': case '.': case '#': break;
case ',':
if (parens <= 0) {
docenv().warning(holder,
"tag.see.malformed_see_tag",
name, text);
return;
}
break;
case ' ': case '\t': case '\n': case CR:
if (parens == 0) { //here onwards the comment starts.
commentstart = i;
i = len;
}
break;
default:
if (!Character.isJavaIdentifierPart(cp)) {
docenv().warning(holder,
"tag.see.illegal_character",
name, ""+cp, text);
}
break;
}
}
if (parens != 0) {
docenv().warning(holder,
"tag.see.malformed_see_tag",
name, text);
return;
}
String seetext = "";
String labeltext = "";
if (commentstart > 0) {
seetext = text.substring(start, commentstart);
labeltext = text.substring(commentstart + 1);
// strip off the white space which can be between seetext and the
// actual label.
for (int i = 0; i < labeltext.length(); i++) {
char ch2 = labeltext.charAt(i);
if (!(ch2 == ' ' || ch2 == '\t' || ch2 == '\n')) {
label = labeltext.substring(i);
break;
}
}
} else {
seetext = text;
label = "";
}
int sharp = seetext.indexOf('#');
if (sharp >= 0) {
// class#member
where = seetext.substring(0, sharp);
what = seetext.substring(sharp + 1);
} else {
if (seetext.indexOf('(') >= 0) {
docenv().warning(holder,
"tag.see.missing_sharp",
name, text);
where = "";
what = seetext;
}
else {
// no member specified, text names class
where = seetext;
what = null;
}
}
}
Find what is referenced by the see also. If possible, sets
referencedClass and referencedMember.
Params: - containingClass – the class containing the comment containing
the tag. May be null, if, for example, it is a package comment.
/**
* Find what is referenced by the see also. If possible, sets
* referencedClass and referencedMember.
*
* @param containingClass the class containing the comment containing
* the tag. May be null, if, for example, it is a package comment.
*/
private void findReferenced(ClassDocImpl containingClass) {
if (where.length() > 0) {
if (containingClass != null) {
referencedClass = containingClass.findClass(where);
} else {
referencedClass = docenv().lookupClass(where);
}
if (referencedClass == null && holder() instanceof ProgramElementDoc) {
referencedClass = docenv().lookupClass(
((ProgramElementDoc) holder()).containingPackage().name() + "." + where);
}
if (referencedClass == null) { /* may just not be in this run */
// check if it's a package name
referencedPackage = docenv().lookupPackage(where);
return;
}
} else {
if (containingClass == null) {
docenv().warning(holder,
"tag.see.class_not_specified",
name, text);
return;
} else {
referencedClass = containingClass;
}
}
where = referencedClass.qualifiedName();
if (what == null) {
return;
} else {
int paren = what.indexOf('(');
String memName = (paren >= 0 ? what.substring(0, paren) : what);
String[] paramarr;
if (paren > 0) {
// has parameter list -- should be method or constructor
paramarr = new ParameterParseMachine(what.
substring(paren, what.length())).parseParameters();
if (paramarr != null) {
referencedMember = findExecutableMember(memName, paramarr,
referencedClass);
} else {
referencedMember = null;
}
} else {
// no parameter list -- should be field
referencedMember = findExecutableMember(memName, null,
referencedClass);
FieldDoc fd = ((ClassDocImpl)referencedClass).
findField(memName);
// when no args given, prefer fields over methods
if (referencedMember == null ||
(fd != null &&
fd.containingClass()
.subclassOf(referencedMember.containingClass()))) {
referencedMember = fd;
}
}
if (referencedMember == null) {
docenv().warning(holder,
"tag.see.can_not_find_member",
name, what, where);
}
}
}
private MemberDoc findReferencedMethod(String memName, String[] paramarr,
ClassDoc referencedClass) {
MemberDoc meth = findExecutableMember(memName, paramarr, referencedClass);
if (meth == null) {
for (ClassDoc nestedClass : referencedClass.innerClasses()) {
meth = findReferencedMethod(memName, paramarr, nestedClass);
if (meth != null) {
return meth;
}
}
}
return null;
}
private MemberDoc findExecutableMember(String memName, String[] paramarr,
ClassDoc referencedClass) {
String className = referencedClass.name();
if (memName.equals(className.substring(className.lastIndexOf(".") + 1))) {
return ((ClassDocImpl)referencedClass).findConstructor(memName,
paramarr);
} else { // it's a method.
return ((ClassDocImpl)referencedClass).findMethod(memName,
paramarr);
}
}
// separate "int, String" from "(int, String)"
// (int i, String s) ==> [0] = "int", [1] = String
// (int[][], String[]) ==> [0] = "int[][]" // [1] = "String[]"
class ParameterParseMachine {
static final int START = 0;
static final int TYPE = 1;
static final int NAME = 2;
static final int TNSPACE = 3; // space between type and name
static final int ARRAYDECORATION = 4;
static final int ARRAYSPACE = 5;
String parameters;
StringBuilder typeId;
ListBuffer<String> paramList;
ParameterParseMachine(String parameters) {
this.parameters = parameters;
this.paramList = new ListBuffer<>();
typeId = new StringBuilder();
}
public String[] parseParameters() {
if (parameters.equals("()")) {
return new String[0];
} // now strip off '(' and ')'
int state = START;
int prevstate = START;
parameters = parameters.substring(1, parameters.length() - 1);
int cp;
for (int index = 0; index < parameters.length(); index += Character.charCount(cp)) {
cp = parameters.codePointAt(index);
switch (state) {
case START:
if (Character.isJavaIdentifierStart(cp)) {
typeId.append(Character.toChars(cp));
state = TYPE;
}
prevstate = START;
break;
case TYPE:
if (Character.isJavaIdentifierPart(cp) || cp == '.') {
typeId.append(Character.toChars(cp));
} else if (cp == '[') {
typeId.append('[');
state = ARRAYDECORATION;
} else if (Character.isWhitespace(cp)) {
state = TNSPACE;
} else if (cp == ',') { // no name, just type
addTypeToParamList();
state = START;
}
prevstate = TYPE;
break;
case TNSPACE:
if (Character.isJavaIdentifierStart(cp)) { // name
if (prevstate == ARRAYDECORATION) {
docenv().warning(holder,
"tag.missing_comma_space",
name,
"(" + parameters + ")");
return (String[])null;
}
addTypeToParamList();
state = NAME;
} else if (cp == '[') {
typeId.append('[');
state = ARRAYDECORATION;
} else if (cp == ',') { // just the type
addTypeToParamList();
state = START;
} // consume rest all
prevstate = TNSPACE;
break;
case ARRAYDECORATION:
if (cp == ']') {
typeId.append(']');
state = TNSPACE;
} else if (!Character.isWhitespace(cp)) {
docenv().warning(holder,
"tag.illegal_char_in_arr_dim",
name,
"(" + parameters + ")");
return (String[])null;
}
prevstate = ARRAYDECORATION;
break;
case NAME:
if (cp == ',') { // just consume everything till ','
state = START;
}
prevstate = NAME;
break;
}
}
if (state == ARRAYDECORATION ||
(state == START && prevstate == TNSPACE)) {
docenv().warning(holder,
"tag.illegal_see_tag",
"(" + parameters + ")");
}
if (typeId.length() > 0) {
paramList.append(typeId.toString());
}
return paramList.toArray(new String[paramList.length()]);
}
void addTypeToParamList() {
if (typeId.length() > 0) {
paramList.append(typeId.toString());
typeId.setLength(0);
}
}
}
Return the kind of this tag.
/**
* Return the kind of this tag.
*/
@Override
public String kind() {
return "@see";
}
Return the label of the see tag.
/**
* Return the label of the see tag.
*/
public String label() {
return label;
}
}