Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import java.util.regex.Pattern;
     18 import java.util.regex.Matcher;
     19 import java.util.ArrayList;
     20 
     21 /**
     22  * Class that represents what you see in an link or see tag.  This is
     23  * factored out of SeeTagInfo so it can be used elsewhere (like AttrTagInfo).
     24  */
     25 public class LinkReference {
     26 
     27     /** The original text. */
     28     public String text;
     29 
     30     /** The kind of this tag, if we have a new suggestion after parsing. */
     31     public String kind;
     32 
     33     /** The user visible text. */
     34     public String label;
     35 
     36     /** The link. */
     37     public String href;
     38 
     39     /** The {@link PackageInfo} if any. */
     40     public PackageInfo packageInfo;
     41 
     42     /** The {@link ClassInfo} if any. */
     43     public ClassInfo classInfo;
     44 
     45     /** The {@link MemberInfo} if any. */
     46     public MemberInfo memberInfo;
     47 
     48     /** The name of the referenced member PackageInfo} if any. */
     49     public String referencedMemberName;
     50 
     51     /** Set to true if everything is a-ok */
     52     public boolean good;
     53 
     54     /**
     55      * regex pattern to use when matching explicit "<a href" reference text
     56      */
     57     private static final Pattern HREF_PATTERN
     58             = Pattern.compile("^<a href=\"([^\"]*)\">([^<]*)</a>[ \n\r\t]*$",
     59                               Pattern.CASE_INSENSITIVE);
     60 
     61     /**
     62      * regex pattern to use when matching double-quoted reference text
     63      */
     64     private static final Pattern QUOTE_PATTERN
     65             = Pattern.compile("^\"([^\"]*)\"[ \n\r\t]*$");
     66 
     67     /**
     68      * Parse and resolve a link string.
     69      *
     70      * @param text the original text
     71      * @param base the class or whatever that this link is on
     72      * @param pos the original position in the source document
     73      * @return a new link reference.  It always returns something.  If there was an
     74      *         error, it logs it and fills in href and label with error text.
     75      */
     76     public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos,
     77                                         boolean printOnErrors) {
     78         LinkReference result = new LinkReference();
     79         result.text = text;
     80 
     81         int index;
     82         int len = text.length();
     83         int pairs = 0;
     84         int pound = -1;
     85         // split the string
     86         done: {
     87             for (index=0; index<len; index++) {
     88                 char c = text.charAt(index);
     89                 switch (c)
     90                 {
     91                     case '(':
     92                         pairs++;
     93                         break;
     94                     case '[':
     95                         pairs++;
     96                         break;
     97                     case ')':
     98                         pairs--;
     99                         break;
    100                     case ']':
    101                         pairs--;
    102                         break;
    103                     case ' ':
    104                     case '\t':
    105                     case '\r':
    106                     case '\n':
    107                         if (pairs == 0) {
    108                             break done;
    109                         }
    110                         break;
    111                     case '#':
    112                         if (pound < 0) {
    113                             pound = index;
    114                         }
    115                         break;
    116                 }
    117             }
    118         }
    119         if (index == len && pairs != 0) {
    120             Errors.error(Errors.UNRESOLVED_LINK, pos,
    121                         "unable to parse link/see tag: " + text.trim());
    122             return result;
    123         }
    124 
    125         int linkend = index;
    126 
    127         for (; index<len; index++) {
    128             char c = text.charAt(index);
    129             if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
    130                 break;
    131             }
    132         }
    133 
    134         result.label = text.substring(index);
    135 
    136         String ref;
    137         String mem;
    138         if (pound == 0) {
    139             ref = null;
    140             mem = text.substring(1, linkend);
    141         }
    142         else if (pound > 0) {
    143             ref = text.substring(0, pound);
    144             mem = text.substring(pound+1, linkend);
    145         }
    146         else {
    147             ref = text.substring(0, linkend);
    148             mem = null;
    149         }
    150 
    151         // parse parameters, if any
    152         String[] params = null;
    153         String[] paramDimensions = null;
    154         if (mem != null) {
    155             index = mem.indexOf('(');
    156             if (index > 0) {
    157                 ArrayList<String> paramList = new ArrayList<String>();
    158                 ArrayList<String> paramDimensionList = new ArrayList<String>();
    159                 len = mem.length();
    160                 int start = index+1;
    161                 final int START = 0;
    162                 final int TYPE = 1;
    163                 final int NAME = 2;
    164                 int dimension = 0;
    165                 int arraypair = 0;
    166                 int state = START;
    167                 int typestart = 0;
    168                 int typeend = -1;
    169                 for (int i=start; i<len; i++) {
    170                     char c = mem.charAt(i);
    171                     switch (state)
    172                     {
    173                         case START:
    174                             if (c!=' ' && c!='\t' && c!='\r' && c!='\n') {
    175                                 state = TYPE;
    176                                 typestart = i;
    177                             }
    178                             break;
    179                         case TYPE:
    180                             if (c == '[') {
    181                                 if (typeend < 0) {
    182                                     typeend = i;
    183                                 }
    184                                 dimension++;
    185                                 arraypair++;
    186                             }
    187                             else if (c == ']') {
    188                                 arraypair--;
    189                             }
    190                             else if (c==' ' || c=='\t' || c=='\r' || c=='\n') {
    191                                 if (typeend < 0) {
    192                                     typeend = i;
    193                                 }
    194                             }
    195                             else {
    196                                 if (typeend >= 0 || c == ')' || c == ',') {
    197                                     if (typeend < 0) {
    198                                         typeend = i;
    199                                     }
    200                                     String s = mem.substring(typestart, typeend);
    201                                     paramList.add(s);
    202                                     s = "";
    203                                     for (int j=0; j<dimension; j++) {
    204                                         s += "[]";
    205                                     }
    206                                     paramDimensionList.add(s);
    207                                     state = START;
    208                                     typeend = -1;
    209                                     dimension = 0;
    210                                     if (c == ',' || c == ')') {
    211                                         state = START;
    212                                     } else {
    213                                         state = NAME;
    214                                     }
    215                                 }
    216                             }
    217                             break;
    218                         case NAME:
    219                             if (c == ',' || c == ')') {
    220                                 state = START;
    221                             }
    222                             break;
    223                     }
    224 
    225                 }
    226                 params = paramList.toArray(new String[paramList.size()]);
    227                 paramDimensions = paramDimensionList.toArray(new String[paramList.size()]);
    228                 mem = mem.substring(0, index);
    229             }
    230         }
    231 
    232         ClassInfo cl = null;
    233         if (base instanceof ClassInfo) {
    234             cl = (ClassInfo)base;
    235         }
    236 
    237         if (ref == null) {
    238             // no class or package was provided, assume it's this class
    239             if (cl != null) {
    240                 result.classInfo = cl;
    241             }
    242         } else {
    243             // they provided something, maybe it's a class or a package
    244             if (cl != null) {
    245                 result.classInfo = cl.extendedFindClass(ref);
    246                 if (result.classInfo == null) {
    247                     result.classInfo = cl.findClass(ref);
    248                 }
    249                 if (result.classInfo == null) {
    250                     result.classInfo = cl.findInnerClass(ref);
    251                 }
    252             }
    253             if (result.classInfo == null) {
    254                 result.classInfo = Converter.obtainClass(ref);
    255             }
    256             if (result.classInfo == null) {
    257                 result.packageInfo = Converter.obtainPackage(ref);
    258             }
    259         }
    260 
    261         if (result.classInfo != null && mem != null) {
    262             // it's either a field or a method, prefer a field
    263             if (params == null) {
    264                 FieldInfo field = result.classInfo.findField(mem);
    265                 // findField looks in containing classes, so it might actually
    266                 // be somewhere else; link to where it really is, not what they
    267                 // typed.
    268                 if (field != null) {
    269                     result.classInfo = field.containingClass();
    270                     result.memberInfo = field;
    271                 }
    272             }
    273             if (result.memberInfo == null) {
    274                 MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions);
    275                 if (method != null) {
    276                     result.classInfo = method.containingClass();
    277                     result.memberInfo = method;
    278                 }
    279             }
    280         }
    281 
    282         result.referencedMemberName = mem;
    283         if (params != null) {
    284             result.referencedMemberName = result.referencedMemberName + '(';
    285             len = params.length;
    286             if (len > 0) {
    287                 len--;
    288                 for (int i=0; i<len; i++) {
    289                     result.referencedMemberName = result.referencedMemberName + params[i]
    290                             + paramDimensions[i] + ", ";
    291                 }
    292                 result.referencedMemberName = result.referencedMemberName + params[len]
    293                         + paramDimensions[len];
    294             }
    295             result.referencedMemberName = result.referencedMemberName + ")";
    296         }
    297 
    298         // debugging spew
    299         if (false) {
    300             result.label = result.label + "/" + ref + "/" + mem + '/';
    301             if (params != null) {
    302                 for (int i=0; i<params.length; i++) {
    303                     result.label += params[i] + "|";
    304                 }
    305             }
    306 
    307             FieldInfo f = (result.memberInfo instanceof FieldInfo)
    308                         ? (FieldInfo)result.memberInfo
    309                         : null;
    310             MethodInfo m = (result.memberInfo instanceof MethodInfo)
    311                         ? (MethodInfo)result.memberInfo
    312                         : null;
    313             result.label = result.label
    314                         + "/package=" + (result.packageInfo!=null?result.packageInfo.name():"")
    315                         + "/class=" + (result.classInfo!=null?result.classInfo.qualifiedName():"")
    316                         + "/field=" + (f!=null?f.name():"")
    317                         + "/method=" + (m!=null?m.name():"");
    318 
    319         }
    320 
    321         MethodInfo method = null;
    322         boolean skipHref = false;
    323 
    324         if (result.memberInfo != null && result.memberInfo.isExecutable()) {
    325            method = (MethodInfo)result.memberInfo;
    326         }
    327 
    328         if (text.startsWith("\"")) {
    329             // literal quoted reference (e.g., a book title)
    330             Matcher matcher = QUOTE_PATTERN.matcher(text);
    331             if (! matcher.matches()) {
    332                 Errors.error(Errors.UNRESOLVED_LINK, pos,
    333                         "unbalanced quoted link/see tag: " + text.trim());
    334                 result.makeError();
    335                 return result;
    336             }
    337             skipHref = true;
    338             result.label = matcher.group(1);
    339             result.kind = "@seeJustLabel";
    340         }
    341         else if (text.startsWith("<")) {
    342             // explicit "<a href" form
    343             Matcher matcher = HREF_PATTERN.matcher(text);
    344             if (! matcher.matches()) {
    345                 Errors.error(Errors.UNRESOLVED_LINK, pos,
    346                         "invalid <a> link/see tag: " + text.trim());
    347                 result.makeError();
    348                 return result;
    349             }
    350             result.href = matcher.group(1);
    351             result.label = matcher.group(2);
    352             result.kind = "@seeHref";
    353         }
    354         else if (result.packageInfo != null) {
    355             result.href = result.packageInfo.htmlPage();
    356             if (result.label.length() == 0) {
    357                 result.href = result.packageInfo.htmlPage();
    358                 result.label = result.packageInfo.name();
    359             }
    360         }
    361         else if (result.classInfo != null && result.referencedMemberName == null) {
    362             // class reference
    363             if (result.label.length() == 0) {
    364                 result.label = result.classInfo.name();
    365             }
    366             result.href = result.classInfo.htmlPage();
    367         }
    368         else if (result.memberInfo != null) {
    369             // member reference
    370             ClassInfo containing = result.memberInfo.containingClass();
    371             if (result.memberInfo.isExecutable()) {
    372                 if (result.referencedMemberName.indexOf('(') < 0) {
    373                     result.referencedMemberName += method.flatSignature();
    374                 }
    375             }
    376             if (result.label.length() == 0) {
    377                 result.label = result.referencedMemberName;
    378             }
    379             result.href = containing.htmlPage() + '#' + result.memberInfo.anchor();
    380         }
    381 
    382         if (result.href == null && !skipHref) {
    383             if (printOnErrors && (base == null || base.checkLevel())) {
    384                 Errors.error(Errors.UNRESOLVED_LINK, pos,
    385                         "Unresolved link/see tag \"" + text.trim()
    386                         + "\" in " + ((base != null) ? base.qualifiedName() : "[null]"));
    387             }
    388             result.makeError();
    389         }
    390         else if (result.memberInfo != null && !result.memberInfo.checkLevel()) {
    391             if (printOnErrors && (base == null || base.checkLevel())) {
    392                 Errors.error(Errors.HIDDEN_LINK, pos,
    393                         "Link to hidden member: " + text.trim());
    394                 result.href = null;
    395             }
    396             result.kind = "@seeJustLabel";
    397         }
    398         else if (result.classInfo != null && !result.classInfo.checkLevel()) {
    399             if (printOnErrors && (base == null || base.checkLevel())) {
    400                 Errors.error(Errors.HIDDEN_LINK, pos,
    401                         "Link to hidden class: " + text.trim() + " label=" + result.label);
    402                 result.href = null;
    403             }
    404             result.kind = "@seeJustLabel";
    405         }
    406         else if (result.packageInfo != null && !result.packageInfo.checkLevel()) {
    407             if (printOnErrors && (base == null || base.checkLevel())) {
    408                 Errors.error(Errors.HIDDEN_LINK, pos,
    409                         "Link to hidden package: " + text.trim());
    410                 result.href = null;
    411             }
    412             result.kind = "@seeJustLabel";
    413         }
    414 
    415         result.good = true;
    416 
    417         return result;
    418     }
    419 
    420     public boolean checkLevel() {
    421         if (memberInfo != null) {
    422             return memberInfo.checkLevel();
    423         }
    424         if (classInfo != null) {
    425             return classInfo.checkLevel();
    426         }
    427         if (packageInfo != null) {
    428             return packageInfo.checkLevel();
    429         }
    430         return false;
    431     }
    432 
    433     /** turn this LinkReference into one with an error message */
    434     private void makeError() {
    435         //this.href = "ERROR(" + this.text.trim() + ")";
    436         this.href = null;
    437         if (this.label == null) {
    438             this.label = "";
    439         }
    440         this.label = "ERROR(" + this.label + "/" + text.trim() + ")";
    441     }
    442 
    443     /** private. **/
    444     private LinkReference() {
    445     }
    446 }
    447