summaryrefslogtreecommitdiffstats
path: root/tools/droiddoc/src/LinkReference.java
diff options
context:
space:
mode:
Diffstat (limited to 'tools/droiddoc/src/LinkReference.java')
-rw-r--r--tools/droiddoc/src/LinkReference.java439
1 files changed, 439 insertions, 0 deletions
diff --git a/tools/droiddoc/src/LinkReference.java b/tools/droiddoc/src/LinkReference.java
new file mode 100644
index 0000000..fa821cf
--- /dev/null
+++ b/tools/droiddoc/src/LinkReference.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+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 {
+
+ /** 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;
+
+ /** 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);
+
+ /**
+ * 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;
+ 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 (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 (ref == null) {
+ // no class or package was provided, assume it's this class
+ if (cl != null) {
+ result.classInfo = cl;
+ }
+ } else {
+ // they provided something, maybe it's a class or a package
+ if (cl != null) {
+ result.classInfo = cl.extendedFindClass(ref);
+ if (result.classInfo == null) {
+ result.classInfo = cl.findClass(ref);
+ }
+ if (result.classInfo == null) {
+ result.classInfo = cl.findInnerClass(ref);
+ }
+ }
+ if (result.classInfo == null) {
+ result.classInfo = Converter.obtainClass(ref);
+ }
+ if (result.classInfo == null) {
+ result.packageInfo = Converter.obtainPackage(ref);
+ }
+ }
+
+ 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);
+ 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 (text.startsWith("\"")) {
+ // literal quoted reference (e.g., a book title)
+ result.label = text.substring(1);
+ skipHref = true;
+ if (!result.label.endsWith("\"")) {
+ Errors.error(Errors.UNRESOLVED_LINK, pos,
+ "unbalanced quoted link/see tag: " + text.trim());
+ result.makeError();
+ return result;
+ }
+ result.label = result.label.substring(0, result.label.length() - 1);
+ result.kind = "@seeJustLabel";
+ }
+ 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";
+ }
+ 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();
+ }
+ }
+ else if (result.classInfo != null && result.referencedMemberName == null) {
+ // class reference
+ if (result.label.length() == 0) {
+ result.label = result.classInfo.name();
+ }
+ result.href = result.classInfo.htmlPage();
+ }
+ 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) {
+ result.label = result.referencedMemberName;
+ }
+ result.href = containing.htmlPage() + '#' + result.memberInfo.anchor();
+ }
+
+ if (result.href == null && !skipHref) {
+ if (printOnErrors && (base == null || base.checkLevel())) {
+ Errors.error(Errors.UNRESOLVED_LINK, pos,
+ "Unresolved link/see tag: " + text.trim());
+ }
+ 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() + ")";
+ }
+
+ /** private. **/
+ private LinkReference() {
+ }
+}