blob: 0bfa42fe2d44d4075b42d539c8201f0805a4de36 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.doclava;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;
/**
* Class that represents what you see in an link or see tag. This is factored out of SeeTagInfo so
* it can be used elsewhere (like AttrTagInfo).
*/
public class LinkReference {
private static final boolean DBG = false;
/** The original text. */
public String text;
/** The kind of this tag, if we have a new suggestion after parsing. */
public String kind;
/** The user visible text. */
public String label;
/** The link. */
public String href;
/** Non-null for federated links */
public String federatedSite;
/** The {@link PackageInfo} if any. */
public PackageInfo packageInfo;
/** The {@link ClassInfo} if any. */
public ClassInfo classInfo;
/** The {@link MemberInfo} if any. */
public MemberInfo memberInfo;
/** The name of the referenced member PackageInfo} if any. */
public String referencedMemberName;
/** Set to true if everything is a-ok */
public boolean good;
/**
* regex pattern to use when matching explicit 'a href' reference text
*/
private static final Pattern HREF_PATTERN =
Pattern.compile("^<a href=\"([^\"]*)\">([^<]*)</a>[ \n\r\t]*$", Pattern.CASE_INSENSITIVE);
/**
* regex pattern to use when matching double-quoted reference text
*/
private static final Pattern QUOTE_PATTERN = Pattern.compile("^\"([^\"]*)\"[ \n\r\t]*$");
/**
* Parse and resolve a link string.
*
* @param text the original text
* @param base the class or whatever that this link is on
* @param pos the original position in the source document
* @return a new link reference. It always returns something. If there was an error, it logs it
* and fills in href and label with error text.
*/
public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos,
boolean printOnErrors) {
LinkReference result = new LinkReference();
result.text = text;
int index;
int len = text.length();
int pairs = 0;
int pound = -1;
// split the string
done: {
for (index = 0; index < len; index++) {
char c = text.charAt(index);
switch (c) {
case '(':
pairs++;
break;
case '[':
pairs++;
break;
case ')':
pairs--;
break;
case ']':
pairs--;
break;
case ' ':
case '\t':
case '\r':
case '\n':
if (pairs == 0) {
break done;
}
break;
case '#':
if (pound < 0) {
pound = index;
}
break;
}
}
}
if (index == len && pairs != 0) {
Errors.error(Errors.UNRESOLVED_LINK, pos, "unable to parse link/see tag: " + text.trim());
return result;
}
int linkend = index;
for (; index < len; index++) {
char c = text.charAt(index);
if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
break;
}
}
result.label = text.substring(index);
String ref;
String mem;
if (pound == 0) {
ref = null;
mem = text.substring(1, linkend);
} else if (pound > 0) {
ref = text.substring(0, pound);
mem = text.substring(pound + 1, linkend);
} else {
ref = text.substring(0, linkend);
mem = null;
}
// parse parameters, if any
String[] params = null;
String[] paramDimensions = null;
boolean varargs = false;
if (mem != null) {
index = mem.indexOf('(');
if (index > 0) {
ArrayList<String> paramList = new ArrayList<String>();
ArrayList<String> paramDimensionList = new ArrayList<String>();
len = mem.length();
int start = index + 1;
final int START = 0;
final int TYPE = 1;
final int NAME = 2;
int dimension = 0;
int arraypair = 0;
int state = START;
int typestart = 0;
int typeend = -1;
for (int i = start; i < len; i++) {
char c = mem.charAt(i);
switch (state) {
case START:
if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
state = TYPE;
typestart = i;
}
break;
case TYPE:
if (c == '.') {
if (mem.length() > i+2 && mem.charAt(i+1) == '.' && mem.charAt(i+2) == '.') {
if (typeend < 0) {
typeend = i;
}
varargs = true;
}
} else if (c == '[') {
if (typeend < 0) {
typeend = i;
}
dimension++;
arraypair++;
} else if (c == ']') {
arraypair--;
} else if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
if (typeend < 0) {
typeend = i;
}
} else {
if (typeend >= 0 || c == ')' || c == ',') {
if (typeend < 0) {
typeend = i;
}
String s = mem.substring(typestart, typeend);
paramList.add(s);
s = "";
for (int j = 0; j < dimension; j++) {
s += "[]";
}
paramDimensionList.add(s);
state = START;
typeend = -1;
dimension = 0;
if (c == ',' || c == ')') {
state = START;
} else {
state = NAME;
}
}
}
break;
case NAME:
if (c == ',' || c == ')') {
state = START;
}
break;
}
}
params = paramList.toArray(new String[paramList.size()]);
paramDimensions = paramDimensionList.toArray(new String[paramList.size()]);
mem = mem.substring(0, index);
}
}
ClassInfo cl = null;
if (base instanceof ClassInfo) {
cl = (ClassInfo) base;
if (DBG) System.out.println("-- chose base as classinfo");
}
if (ref == null) {
if (DBG) System.out.println("-- ref == null");
// no class or package was provided, assume it's this class
if (cl != null) {
if (DBG) System.out.println("-- assumed to be cl");
result.classInfo = cl;
}
} else {
if (DBG) System.out.println("-- they provided ref = " + ref);
// they provided something, maybe it's a class or a package
if (cl != null) {
try {
if (DBG) System.out.println("-- cl non-null");
result.classInfo = cl.extendedFindClass(ref);
if (result.classInfo == null) {
if (DBG) System.out.println("-- cl.extendedFindClass was null");
result.classInfo = cl.findClass(ref);
}
if (result.classInfo == null) {
if (DBG) System.out.println("-- cl.findClass was null");
result.classInfo = cl.findInnerClass(ref);
if (DBG) if (result.classInfo == null) System.out.println("-- cl.findInnerClass was null");
}
} catch (RuntimeException e) {
throw new RuntimeException("Failed to resolve class at " + pos, e);
}
}
if (result.classInfo == null) {
if (DBG) System.out.println("-- hitting up the Converter.obtainclass");
result.classInfo = Converter.obtainClass(ref);
}
if (result.classInfo == null) {
if (DBG) System.out.println("-- Converter.obtainClass was null");
result.packageInfo = Converter.obtainPackage(ref);
}
}
if (result.classInfo == null) {
if (DBG) System.out.println("-- NO CLASS INFO");
} else {
Doclava.federationTagger.tag(result.classInfo);
for (FederatedSite site : result.classInfo.getFederatedReferences()) {
if (DBG) System.out.println("-- reg link = " + result.classInfo.htmlPage());
if (DBG) System.out.println("-- fed link = " +
site.linkFor(result.classInfo.htmlPage()));
}
}
if (result.classInfo != null && mem != null) {
// it's either a field or a method, prefer a field
if (params == null) {
FieldInfo field = result.classInfo.findField(mem);
// findField looks in containing classes, so it might actually
// be somewhere else; link to where it really is, not what they
// typed.
if (field != null) {
result.classInfo = field.containingClass();
result.memberInfo = field;
}
}
if (result.memberInfo == null) {
MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions, varargs);
if (method != null) {
result.classInfo = method.containingClass();
result.memberInfo = method;
}
}
}
result.referencedMemberName = mem;
if (params != null) {
result.referencedMemberName = result.referencedMemberName + '(';
len = params.length;
if (len > 0) {
len--;
for (int i = 0; i < len; i++) {
result.referencedMemberName =
result.referencedMemberName + params[i] + paramDimensions[i] + ", ";
}
result.referencedMemberName =
result.referencedMemberName + params[len] + paramDimensions[len];
}
result.referencedMemberName = result.referencedMemberName + ")";
}
// debugging spew
if (false) {
result.label = result.label + "/" + ref + "/" + mem + '/';
if (params != null) {
for (int i = 0; i < params.length; i++) {
result.label += params[i] + "|";
}
}
FieldInfo f = (result.memberInfo instanceof FieldInfo) ? (FieldInfo) result.memberInfo : null;
MethodInfo m =
(result.memberInfo instanceof MethodInfo) ? (MethodInfo) result.memberInfo : null;
result.label =
result.label + "/package="
+ (result.packageInfo != null ? result.packageInfo.name() : "") + "/class="
+ (result.classInfo != null ? result.classInfo.qualifiedName() : "") + "/field="
+ (f != null ? f.name() : "") + "/method=" + (m != null ? m.name() : "");
}
MethodInfo method = null;
boolean skipHref = false;
if (result.memberInfo != null && result.memberInfo.isExecutable()) {
method = (MethodInfo) result.memberInfo;
}
if (DBG) System.out.println("----- label = " + result.label + ", text = '" + text + "'");
if (text.startsWith("\"")) {
// literal quoted reference (e.g., a book title)
Matcher matcher = QUOTE_PATTERN.matcher(text);
if (!matcher.matches()) {
Errors.error(Errors.UNRESOLVED_LINK, pos, "unbalanced quoted link/see tag: " + text.trim());
result.makeError();
return result;
}
skipHref = true;
result.label = matcher.group(1);
result.kind = "@seeJustLabel";
if (DBG) System.out.println(" ---- literal quoted reference");
} else if (text.startsWith("<")) {
// explicit "<a href" form
Matcher matcher = HREF_PATTERN.matcher(text);
if (!matcher.matches()) {
Errors.error(Errors.UNRESOLVED_LINK, pos, "invalid <a> link/see tag: " + text.trim());
result.makeError();
return result;
}
result.href = matcher.group(1);
result.label = matcher.group(2);
result.kind = "@seeHref";
if (DBG) System.out.println(" ---- explicit href reference");
} else if (result.packageInfo != null) {
result.href = result.packageInfo.htmlPage();
if (result.label.length() == 0) {
result.href = result.packageInfo.htmlPage();
result.label = result.packageInfo.name();
}
if (DBG) System.out.println(" ---- packge reference");
} else if (result.classInfo != null && result.referencedMemberName == null) {
// class reference
if (result.label.length() == 0) {
result.label = result.classInfo.name();
}
setHref(result, result.classInfo, null);
if (DBG) System.out.println(" ---- class reference");
} else if (result.memberInfo != null) {
// member reference
ClassInfo containing = result.memberInfo.containingClass();
if (result.memberInfo.isExecutable()) {
if (result.referencedMemberName.indexOf('(') < 0) {
result.referencedMemberName += method.flatSignature();
}
}
if (result.label.length() == 0) {
// Qualify labels that link beyond the base context
final boolean beyondBase = base != null && containing != null
&& !base.qualifiedName().equals(containing.qualifiedName());
if (beyondBase) {
result.label = containing.name() + "." + result.referencedMemberName;
} else {
result.label = result.referencedMemberName;
}
}
setHref(result, containing, result.memberInfo.anchor());
if (DBG) System.out.println(" ---- member reference");
}
if (DBG) System.out.println(" --- href = '" + result.href + "'");
if (result.href == null && !skipHref) {
if (printOnErrors && (base == null || base.checkLevel())) {
Errors.error(Errors.UNRESOLVED_LINK, pos, "Unresolved link/see tag \"" + text.trim()
+ "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
}
result.makeError();
} else if (result.memberInfo != null && !result.memberInfo.checkLevel()) {
if (printOnErrors && (base == null || base.checkLevel())) {
Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden member: " + text.trim());
result.href = null;
}
result.kind = "@seeJustLabel";
} else if (result.classInfo != null && !result.classInfo.checkLevel()) {
if (printOnErrors && (base == null || base.checkLevel())) {
Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden class: " + text.trim() + " label="
+ result.label);
result.href = null;
}
result.kind = "@seeJustLabel";
} else if (result.packageInfo != null && !result.packageInfo.checkLevel()) {
if (printOnErrors && (base == null || base.checkLevel())) {
Errors.error(Errors.HIDDEN_LINK, pos, "Link to hidden package: " + text.trim());
result.href = null;
}
result.kind = "@seeJustLabel";
}
result.good = true;
return result;
}
public boolean checkLevel() {
if (memberInfo != null) {
return memberInfo.checkLevel();
}
if (classInfo != null) {
return classInfo.checkLevel();
}
if (packageInfo != null) {
return packageInfo.checkLevel();
}
return false;
}
/** turn this LinkReference into one with an error message */
private void makeError() {
// this.href = "ERROR(" + this.text.trim() + ")";
this.href = null;
if (this.label == null) {
this.label = "";
}
this.label = "ERROR(" + this.label + "/" + text.trim() + ")";
}
static private void setHref(LinkReference reference, ClassInfo info, String member) {
String htmlPage = info.htmlPage();
if (member != null) {
htmlPage = htmlPage + "#" + member;
}
Doclava.federationTagger.tag(info);
if (!info.getFederatedReferences().isEmpty()) {
FederatedSite site = info.getFederatedReferences().iterator().next();
reference.href = site.linkFor(htmlPage);
reference.federatedSite = site.name();
} else {
reference.href = htmlPage;
}
}
/** private. **/
private LinkReference() {}
}