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 */ parse(String text, ContainerInfo base, SourcePositionInfo pos, boolean printOnErrors)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 checkLevel()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 */ makeError()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. **/ LinkReference()444 private LinkReference() { 445 } 446 } 447