diff options
Diffstat (limited to 'tools/droiddoc/src/LinkReference.java')
-rw-r--r-- | tools/droiddoc/src/LinkReference.java | 439 |
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() { + } +} |