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 * Parse and resolve a link string. 63 * 64 * @param text the original text 65 * @param base the class or whatever that this link is on 66 * @param pos the original position in the source document 67 * @return a new link reference. It always returns something. If there was an 68 * error, it logs it and fills in href and label with error text. 69 */ parse(String text, ContainerInfo base, SourcePositionInfo pos, boolean printOnErrors)70 public static LinkReference parse(String text, ContainerInfo base, SourcePositionInfo pos, 71 boolean printOnErrors) { 72 LinkReference result = new LinkReference(); 73 result.text = text; 74 75 int index; 76 int len = text.length(); 77 int pairs = 0; 78 int pound = -1; 79 // split the string 80 done: { 81 for (index=0; index<len; index++) { 82 char c = text.charAt(index); 83 switch (c) 84 { 85 case '(': 86 pairs++; 87 break; 88 case '[': 89 pairs++; 90 break; 91 case ')': 92 pairs--; 93 break; 94 case ']': 95 pairs--; 96 break; 97 case ' ': 98 case '\t': 99 case '\r': 100 case '\n': 101 if (pairs == 0) { 102 break done; 103 } 104 break; 105 case '#': 106 if (pound < 0) { 107 pound = index; 108 } 109 break; 110 } 111 } 112 } 113 if (index == len && pairs != 0) { 114 Errors.error(Errors.UNRESOLVED_LINK, pos, 115 "unable to parse link/see tag: " + text.trim()); 116 return result; 117 } 118 119 int linkend = index; 120 121 for (; index<len; index++) { 122 char c = text.charAt(index); 123 if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { 124 break; 125 } 126 } 127 128 result.label = text.substring(index); 129 130 String ref; 131 String mem; 132 if (pound == 0) { 133 ref = null; 134 mem = text.substring(1, linkend); 135 } 136 else if (pound > 0) { 137 ref = text.substring(0, pound); 138 mem = text.substring(pound+1, linkend); 139 } 140 else { 141 ref = text.substring(0, linkend); 142 mem = null; 143 } 144 145 // parse parameters, if any 146 String[] params = null; 147 String[] paramDimensions = null; 148 if (mem != null) { 149 index = mem.indexOf('('); 150 if (index > 0) { 151 ArrayList<String> paramList = new ArrayList<String>(); 152 ArrayList<String> paramDimensionList = new ArrayList<String>(); 153 len = mem.length(); 154 int start = index+1; 155 final int START = 0; 156 final int TYPE = 1; 157 final int NAME = 2; 158 int dimension = 0; 159 int arraypair = 0; 160 int state = START; 161 int typestart = 0; 162 int typeend = -1; 163 for (int i=start; i<len; i++) { 164 char c = mem.charAt(i); 165 switch (state) 166 { 167 case START: 168 if (c!=' ' && c!='\t' && c!='\r' && c!='\n') { 169 state = TYPE; 170 typestart = i; 171 } 172 break; 173 case TYPE: 174 if (c == '[') { 175 if (typeend < 0) { 176 typeend = i; 177 } 178 dimension++; 179 arraypair++; 180 } 181 else if (c == ']') { 182 arraypair--; 183 } 184 else if (c==' ' || c=='\t' || c=='\r' || c=='\n') { 185 if (typeend < 0) { 186 typeend = i; 187 } 188 } 189 else { 190 if (typeend >= 0 || c == ')' || c == ',') { 191 if (typeend < 0) { 192 typeend = i; 193 } 194 String s = mem.substring(typestart, typeend); 195 paramList.add(s); 196 s = ""; 197 for (int j=0; j<dimension; j++) { 198 s += "[]"; 199 } 200 paramDimensionList.add(s); 201 state = START; 202 typeend = -1; 203 dimension = 0; 204 if (c == ',' || c == ')') { 205 state = START; 206 } else { 207 state = NAME; 208 } 209 } 210 } 211 break; 212 case NAME: 213 if (c == ',' || c == ')') { 214 state = START; 215 } 216 break; 217 } 218 219 } 220 params = paramList.toArray(new String[paramList.size()]); 221 paramDimensions = paramDimensionList.toArray(new String[paramList.size()]); 222 mem = mem.substring(0, index); 223 } 224 } 225 226 ClassInfo cl = null; 227 if (base instanceof ClassInfo) { 228 cl = (ClassInfo)base; 229 } 230 231 if (ref == null) { 232 // no class or package was provided, assume it's this class 233 if (cl != null) { 234 result.classInfo = cl; 235 } 236 } else { 237 // they provided something, maybe it's a class or a package 238 if (cl != null) { 239 result.classInfo = cl.extendedFindClass(ref); 240 if (result.classInfo == null) { 241 result.classInfo = cl.findClass(ref); 242 } 243 if (result.classInfo == null) { 244 result.classInfo = cl.findInnerClass(ref); 245 } 246 } 247 if (result.classInfo == null) { 248 result.classInfo = Converter.obtainClass(ref); 249 } 250 if (result.classInfo == null) { 251 result.packageInfo = Converter.obtainPackage(ref); 252 } 253 } 254 255 if (result.classInfo != null && mem != null) { 256 // it's either a field or a method, prefer a field 257 if (params == null) { 258 FieldInfo field = result.classInfo.findField(mem); 259 // findField looks in containing classes, so it might actually 260 // be somewhere else; link to where it really is, not what they 261 // typed. 262 if (field != null) { 263 result.classInfo = field.containingClass(); 264 result.memberInfo = field; 265 } 266 } 267 if (result.memberInfo == null) { 268 MethodInfo method = result.classInfo.findMethod(mem, params, paramDimensions); 269 if (method != null) { 270 result.classInfo = method.containingClass(); 271 result.memberInfo = method; 272 } 273 } 274 } 275 276 result.referencedMemberName = mem; 277 if (params != null) { 278 result.referencedMemberName = result.referencedMemberName + '('; 279 len = params.length; 280 if (len > 0) { 281 len--; 282 for (int i=0; i<len; i++) { 283 result.referencedMemberName = result.referencedMemberName + params[i] 284 + paramDimensions[i] + ", "; 285 } 286 result.referencedMemberName = result.referencedMemberName + params[len] 287 + paramDimensions[len]; 288 } 289 result.referencedMemberName = result.referencedMemberName + ")"; 290 } 291 292 // debugging spew 293 if (false) { 294 result.label = result.label + "/" + ref + "/" + mem + '/'; 295 if (params != null) { 296 for (int i=0; i<params.length; i++) { 297 result.label += params[i] + "|"; 298 } 299 } 300 301 FieldInfo f = (result.memberInfo instanceof FieldInfo) 302 ? (FieldInfo)result.memberInfo 303 : null; 304 MethodInfo m = (result.memberInfo instanceof MethodInfo) 305 ? (MethodInfo)result.memberInfo 306 : null; 307 result.label = result.label 308 + "/package=" + (result.packageInfo!=null?result.packageInfo.name():"") 309 + "/class=" + (result.classInfo!=null?result.classInfo.qualifiedName():"") 310 + "/field=" + (f!=null?f.name():"") 311 + "/method=" + (m!=null?m.name():""); 312 313 } 314 315 MethodInfo method = null; 316 boolean skipHref = false; 317 318 if (result.memberInfo != null && result.memberInfo.isExecutable()) { 319 method = (MethodInfo)result.memberInfo; 320 } 321 322 if (text.startsWith("\"")) { 323 // literal quoted reference (e.g., a book title) 324 result.label = text.substring(1); 325 skipHref = true; 326 if (!result.label.endsWith("\"")) { 327 Errors.error(Errors.UNRESOLVED_LINK, pos, 328 "unbalanced quoted link/see tag: " + text.trim()); 329 result.makeError(); 330 return result; 331 } 332 result.label = result.label.substring(0, result.label.length() - 1); 333 result.kind = "@seeJustLabel"; 334 } 335 else if (text.startsWith("<")) { 336 // explicit "<a href" form 337 Matcher matcher = HREF_PATTERN.matcher(text); 338 if (! matcher.matches()) { 339 Errors.error(Errors.UNRESOLVED_LINK, pos, 340 "invalid <a> link/see tag: " + text.trim()); 341 result.makeError(); 342 return result; 343 } 344 result.href = matcher.group(1); 345 result.label = matcher.group(2); 346 result.kind = "@seeHref"; 347 } 348 else if (result.packageInfo != null) { 349 result.href = result.packageInfo.htmlPage(); 350 if (result.label.length() == 0) { 351 result.href = result.packageInfo.htmlPage(); 352 result.label = result.packageInfo.name(); 353 } 354 } 355 else if (result.classInfo != null && result.referencedMemberName == null) { 356 // class reference 357 if (result.label.length() == 0) { 358 result.label = result.classInfo.name(); 359 } 360 result.href = result.classInfo.htmlPage(); 361 } 362 else if (result.memberInfo != null) { 363 // member reference 364 ClassInfo containing = result.memberInfo.containingClass(); 365 if (result.memberInfo.isExecutable()) { 366 if (result.referencedMemberName.indexOf('(') < 0) { 367 result.referencedMemberName += method.flatSignature(); 368 } 369 } 370 if (result.label.length() == 0) { 371 result.label = result.referencedMemberName; 372 } 373 result.href = containing.htmlPage() + '#' + result.memberInfo.anchor(); 374 } 375 376 if (result.href == null && !skipHref) { 377 if (printOnErrors && (base == null || base.checkLevel())) { 378 Errors.error(Errors.UNRESOLVED_LINK, pos, 379 "Unresolved link/see tag \"" + text.trim() 380 + "\" in " + ((base != null) ? base.qualifiedName() : "[null]")); 381 } 382 result.makeError(); 383 } 384 else if (result.memberInfo != null && !result.memberInfo.checkLevel()) { 385 if (printOnErrors && (base == null || base.checkLevel())) { 386 Errors.error(Errors.HIDDEN_LINK, pos, 387 "Link to hidden member: " + text.trim()); 388 result.href = null; 389 } 390 result.kind = "@seeJustLabel"; 391 } 392 else if (result.classInfo != null && !result.classInfo.checkLevel()) { 393 if (printOnErrors && (base == null || base.checkLevel())) { 394 Errors.error(Errors.HIDDEN_LINK, pos, 395 "Link to hidden class: " + text.trim() + " label=" + result.label); 396 result.href = null; 397 } 398 result.kind = "@seeJustLabel"; 399 } 400 else if (result.packageInfo != null && !result.packageInfo.checkLevel()) { 401 if (printOnErrors && (base == null || base.checkLevel())) { 402 Errors.error(Errors.HIDDEN_LINK, pos, 403 "Link to hidden package: " + text.trim()); 404 result.href = null; 405 } 406 result.kind = "@seeJustLabel"; 407 } 408 409 result.good = true; 410 411 return result; 412 } 413 checkLevel()414 public boolean checkLevel() { 415 if (memberInfo != null) { 416 return memberInfo.checkLevel(); 417 } 418 if (classInfo != null) { 419 return classInfo.checkLevel(); 420 } 421 if (packageInfo != null) { 422 return packageInfo.checkLevel(); 423 } 424 return false; 425 } 426 427 /** turn this LinkReference into one with an error message */ makeError()428 private void makeError() { 429 //this.href = "ERROR(" + this.text.trim() + ")"; 430 this.href = null; 431 if (this.label == null) { 432 this.label = ""; 433 } 434 this.label = "ERROR(" + this.label + "/" + text.trim() + ")"; 435 } 436 437 /** private. **/ LinkReference()438 private LinkReference() { 439 } 440 } 441