| /* |
| * 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) { |
| 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"); |
| } |
| } |
| 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) { |
| 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() {} |
| } |