1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php 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 package com.android.ide.common.resources.platform; 18 19 import static com.android.SdkConstants.DOT_LAYOUT_PARAMS; 20 import static com.android.ide.eclipse.adt.AdtConstants.DOC_HIDE; 21 22 import com.android.ide.common.api.IAttributeInfo.Format; 23 import com.android.ide.common.resources.platform.ViewClassInfo.LayoutParamsInfo; 24 import com.android.ide.eclipse.adt.AdtUtils; 25 import com.android.utils.ILogger; 26 import com.google.common.collect.Maps; 27 28 import org.w3c.dom.Document; 29 import org.w3c.dom.Node; 30 import org.xml.sax.SAXException; 31 32 import java.io.File; 33 import java.io.IOException; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.EnumSet; 37 import java.util.HashMap; 38 import java.util.Locale; 39 import java.util.Map; 40 import java.util.Map.Entry; 41 42 import javax.xml.parsers.DocumentBuilder; 43 import javax.xml.parsers.DocumentBuilderFactory; 44 import javax.xml.parsers.ParserConfigurationException; 45 46 /** 47 * Parser for attributes description files. 48 */ 49 public final class AttrsXmlParser { 50 51 public static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest"; //$NON-NLS-1$ 52 53 private Document mDocument; 54 private String mOsAttrsXmlPath; 55 56 // all attributes that have the same name are supposed to have the same 57 // parameters so we'll keep a cache of them to avoid processing them twice. 58 private Map<String, AttributeInfo> mAttributeMap; 59 60 /** Map of all attribute names for a given element */ 61 private final Map<String, DeclareStyleableInfo> mStyleMap = 62 new HashMap<String, DeclareStyleableInfo>(); 63 64 /** Map from format name (lower case) to the uppercase version */ 65 private Map<String, Format> mFormatNames = new HashMap<String, Format>(10); 66 67 /** 68 * Map of all (constant, value) pairs for attributes of format enum or flag. 69 * E.g. for attribute name=gravity, this tells us there's an enum/flag called "center" 70 * with value 0x11. 71 */ 72 private Map<String, Map<String, Integer>> mEnumFlagValues; 73 74 /** 75 * A logger object. Must not be null. 76 */ 77 private final ILogger mLog; 78 79 /** 80 * Creates a new {@link AttrsXmlParser}, set to load things from the given 81 * XML file. Nothing has been parsed yet. Callers should call {@link #preload()} 82 * next. 83 * 84 * @param osAttrsXmlPath The path of the <code>attrs.xml</code> file to parse. 85 * Must not be null. Should point to an existing valid XML document. 86 * @param log A logger object. Must not be null. 87 * @param expectedAttributeCount expected number of attributes in the file 88 */ AttrsXmlParser(String osAttrsXmlPath, ILogger log, int expectedAttributeCount)89 public AttrsXmlParser(String osAttrsXmlPath, ILogger log, int expectedAttributeCount) { 90 this(osAttrsXmlPath, null /* inheritableAttributes */, log, expectedAttributeCount); 91 } 92 93 /** 94 * Returns the parsed map of attribute infos 95 * 96 * @return a map from string name to {@link AttributeInfo} 97 */ getAttributeMap()98 public Map<String, AttributeInfo> getAttributeMap() { 99 return mAttributeMap; 100 } 101 102 /** 103 * Creates a new {@link AttrsXmlParser} set to load things from the given 104 * XML file. 105 * <p/> 106 * If inheritableAttributes is non-null, it must point to a preloaded 107 * {@link AttrsXmlParser} which attributes will be used for this one. Since 108 * already defined attributes are not modifiable, they are thus "inherited". 109 * 110 * @param osAttrsXmlPath The path of the <code>attrs.xml</code> file to parse. 111 * Must not be null. Should point to an existing valid XML document. 112 * @param inheritableAttributes An optional parser with attributes to inherit. Can be null. 113 * If not null, the parser must have had its {@link #preload()} method 114 * invoked prior to being used here. 115 * @param log A logger object. Must not be null. 116 * @param expectedAttributeCount expected number of attributes in the file 117 */ AttrsXmlParser( String osAttrsXmlPath, AttrsXmlParser inheritableAttributes, ILogger log, int expectedAttributeCount)118 public AttrsXmlParser( 119 String osAttrsXmlPath, 120 AttrsXmlParser inheritableAttributes, 121 ILogger log, 122 int expectedAttributeCount) { 123 mOsAttrsXmlPath = osAttrsXmlPath; 124 mLog = log; 125 126 assert osAttrsXmlPath != null; 127 assert log != null; 128 129 mAttributeMap = Maps.newHashMapWithExpectedSize(expectedAttributeCount); 130 if (inheritableAttributes == null) { 131 mEnumFlagValues = new HashMap<String, Map<String,Integer>>(); 132 } else { 133 mAttributeMap.putAll(inheritableAttributes.mAttributeMap); 134 mEnumFlagValues = new HashMap<String, Map<String,Integer>>( 135 inheritableAttributes.mEnumFlagValues); 136 } 137 138 // Pre-compute the set of format names such that we don't have to compute the uppercase 139 // version of the same format string names again and again 140 for (Format f : Format.values()) { 141 mFormatNames.put(f.name().toLowerCase(Locale.US), f); 142 } 143 } 144 145 /** 146 * Returns the OS path of the attrs.xml file parsed. 147 */ getOsAttrsXmlPath()148 public String getOsAttrsXmlPath() { 149 return mOsAttrsXmlPath; 150 } 151 152 /** 153 * Preloads the document, parsing all attributes and declared styles. 154 * 155 * @return Self, for command chaining. 156 */ preload()157 public AttrsXmlParser preload() { 158 Document doc = getDocument(); 159 160 if (doc == null) { 161 mLog.warning("Failed to find %1$s", //$NON-NLS-1$ 162 mOsAttrsXmlPath); 163 return this; 164 } 165 166 Node res = doc.getFirstChild(); 167 while (res != null && 168 res.getNodeType() != Node.ELEMENT_NODE && 169 !res.getNodeName().equals("resources")) { //$NON-NLS-1$ 170 res = res.getNextSibling(); 171 } 172 173 if (res == null) { 174 mLog.warning("Failed to find a <resources> node in %1$s", //$NON-NLS-1$ 175 mOsAttrsXmlPath); 176 return this; 177 } 178 179 parseResources(res); 180 return this; 181 } 182 183 /** 184 * Loads all attributes & javadoc for the view class info based on the class name. 185 */ loadViewAttributes(ViewClassInfo info)186 public void loadViewAttributes(ViewClassInfo info) { 187 if (getDocument() != null) { 188 String xmlName = info.getShortClassName(); 189 DeclareStyleableInfo style = mStyleMap.get(xmlName); 190 if (style != null) { 191 String definedBy = info.getFullClassName(); 192 AttributeInfo[] attributes = style.getAttributes(); 193 for (AttributeInfo attribute : attributes) { 194 if (attribute.getDefinedBy() == null) { 195 attribute.setDefinedBy(definedBy); 196 } 197 } 198 info.setAttributes(attributes); 199 info.setJavaDoc(style.getJavaDoc()); 200 } 201 } 202 } 203 204 /** 205 * Loads all attributes for the layout data info based on the class name. 206 */ loadLayoutParamsAttributes(LayoutParamsInfo info)207 public void loadLayoutParamsAttributes(LayoutParamsInfo info) { 208 if (getDocument() != null) { 209 // Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout". 210 ViewClassInfo viewLayoutClass = info.getViewLayoutClass(); 211 String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$ 212 viewLayoutClass.getShortClassName(), 213 info.getShortClassName()); 214 xmlName = AdtUtils.stripSuffix(xmlName, "Params"); //$NON-NLS-1$ 215 216 DeclareStyleableInfo style = mStyleMap.get(xmlName); 217 if (style != null) { 218 // For defined by, use the actual class name, e.g. 219 // android.widget.LinearLayout.LayoutParams 220 String definedBy = viewLayoutClass.getFullClassName() + DOT_LAYOUT_PARAMS; 221 AttributeInfo[] attributes = style.getAttributes(); 222 for (AttributeInfo attribute : attributes) { 223 if (attribute.getDefinedBy() == null) { 224 attribute.setDefinedBy(definedBy); 225 } 226 } 227 info.setAttributes(attributes); 228 } 229 } 230 } 231 232 /** 233 * Returns a list of all <code>declare-styleable</code> found in the XML file. 234 */ getDeclareStyleableList()235 public Map<String, DeclareStyleableInfo> getDeclareStyleableList() { 236 return Collections.unmodifiableMap(mStyleMap); 237 } 238 239 /** 240 * Returns a map of all enum and flag constants sorted by parent attribute name. 241 * The map is attribute_name => (constant_name => integer_value). 242 */ getEnumFlagValues()243 public Map<String, Map<String, Integer>> getEnumFlagValues() { 244 return mEnumFlagValues; 245 } 246 247 //------------------------- 248 249 /** 250 * Creates an XML document from the attrs.xml OS path. 251 * May return null if the file doesn't exist or cannot be parsed. 252 */ getDocument()253 private Document getDocument() { 254 if (mDocument == null) { 255 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 256 factory.setIgnoringComments(false); 257 try { 258 DocumentBuilder builder = factory.newDocumentBuilder(); 259 mDocument = builder.parse(new File(mOsAttrsXmlPath)); 260 } catch (ParserConfigurationException e) { 261 mLog.error(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$ 262 mOsAttrsXmlPath); 263 } catch (SAXException e) { 264 mLog.error(e, "Failed to parse XML document %1$s", //$NON-NLS-1$ 265 mOsAttrsXmlPath); 266 } catch (IOException e) { 267 mLog.error(e, "Failed to read XML document %1$s", //$NON-NLS-1$ 268 mOsAttrsXmlPath); 269 } 270 } 271 return mDocument; 272 } 273 274 /** 275 * Finds all the <declare-styleable> and <attr> nodes 276 * in the top <resources> node. 277 */ parseResources(Node res)278 private void parseResources(Node res) { 279 280 Map<String, String> unknownParents = new HashMap<String, String>(); 281 282 Node lastComment = null; 283 for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) { 284 switch (node.getNodeType()) { 285 case Node.COMMENT_NODE: 286 lastComment = node; 287 break; 288 case Node.ELEMENT_NODE: 289 if (node.getNodeName().equals("declare-styleable")) { //$NON-NLS-1$ 290 Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$ 291 if (nameNode != null) { 292 String name = nameNode.getNodeValue(); 293 294 Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$ 295 String parents = parentNode == null ? null : parentNode.getNodeValue(); 296 297 if (name != null && !mStyleMap.containsKey(name)) { 298 DeclareStyleableInfo style = parseDeclaredStyleable(name, node); 299 if (parents != null) { 300 String[] parentsArray = 301 parseStyleableParents(parents, mStyleMap, unknownParents); 302 style.setParents(parentsArray); 303 } 304 mStyleMap.put(name, style); 305 unknownParents.remove(name); 306 if (lastComment != null) { 307 String nodeValue = lastComment.getNodeValue(); 308 if (nodeValue.contains(DOC_HIDE)) { 309 mStyleMap.remove(name); 310 } else { 311 style.setJavaDoc(parseJavadoc(nodeValue)); 312 } 313 } 314 } 315 } 316 } else if (node.getNodeName().equals("attr")) { //$NON-NLS-1$ 317 parseAttr(node, lastComment); 318 } 319 lastComment = null; 320 break; 321 } 322 } 323 324 // If we have any unknown parent, re-create synthetic styleable for them. 325 for (Entry<String, String> entry : unknownParents.entrySet()) { 326 String name = entry.getKey(); 327 String parent = entry.getValue(); 328 329 DeclareStyleableInfo style = new DeclareStyleableInfo(name, (AttributeInfo[])null); 330 if (parent != null) { 331 style.setParents(new String[] { parent }); 332 } 333 mStyleMap.put(name, style); 334 335 // Simplify parents names. See SDK Bug 3125910. 336 // Implementation detail: that since we want to delete and add to the map, 337 // we can't just use an iterator. 338 for (String key : new ArrayList<String>(mStyleMap.keySet())) { 339 if (key.startsWith(name) && !key.equals(name)) { 340 // We found a child which name starts with the full name of the 341 // parent. Simplify the children name. 342 String newName = ANDROID_MANIFEST_STYLEABLE + key.substring(name.length()); 343 344 DeclareStyleableInfo newStyle = 345 new DeclareStyleableInfo(newName, mStyleMap.get(key)); 346 mStyleMap.remove(key); 347 mStyleMap.put(newName, newStyle); 348 } 349 } 350 } 351 } 352 353 /** 354 * Parses the "parents" attribute from a <declare-styleable>. 355 * <p/> 356 * The syntax is the following: 357 * <pre> 358 * parent[.parent]* [[space|,] parent[.parent]* ] 359 * </pre> 360 * <p/> 361 * In English: </br> 362 * - There can be one or more parents, separated by whitespace or commas. </br> 363 * - Whitespace is ignored and trimmed. </br> 364 * - A parent name is actually composed of one or more identifiers joined by a dot. 365 * <p/> 366 * Styleables do not usually need to declare their parent chain (e.g. the grand-parents 367 * of a parent.) Parent names are unique, so in most cases a styleable will only declare 368 * its immediate parent. 369 * <p/> 370 * However it is possible for a styleable's parent to not exist, e.g. if you have a 371 * styleable "A" that is the root and then styleable "C" declares its parent to be "A.B". 372 * In this case we record "B" as the parent, even though it is unknown and will never be 373 * known. Any parent that is currently not in the knownParent map is thus added to the 374 * unknownParent set. The caller will remove the name from the unknownParent set when it 375 * sees a declaration for it. 376 * 377 * @param parents The parents string to parse. Must not be null or empty. 378 * @param knownParents The map of all declared styles known so far. 379 * @param unknownParents A map of all unknown parents collected here. 380 * @return The array of terminal parent names parsed from the parents string. 381 */ parseStyleableParents(String parents, Map<String, DeclareStyleableInfo> knownParents, Map<String, String> unknownParents)382 private String[] parseStyleableParents(String parents, 383 Map<String, DeclareStyleableInfo> knownParents, 384 Map<String, String> unknownParents) { 385 386 ArrayList<String> result = new ArrayList<String>(); 387 388 for (String parent : parents.split("[ \t\n\r\f,|]")) { //$NON-NLS-1$ 389 parent = parent.trim(); 390 if (parent.length() == 0) { 391 continue; 392 } 393 if (parent.indexOf('.') >= 0) { 394 // This is a grand-parent/parent chain. Make sure we know about the 395 // parents and only record the terminal one. 396 String last = null; 397 for (String name : parent.split("\\.")) { //$NON-NLS-1$ 398 if (name.length() > 0) { 399 if (!knownParents.containsKey(name)) { 400 // Record this unknown parent and its grand parent. 401 unknownParents.put(name, last); 402 } 403 last = name; 404 } 405 } 406 parent = last; 407 } 408 409 result.add(parent); 410 } 411 412 return result.toArray(new String[result.size()]); 413 } 414 415 /** 416 * Parses an <attr> node and convert it into an {@link AttributeInfo} if it is valid. 417 */ parseAttr(Node attrNode, Node lastComment)418 private AttributeInfo parseAttr(Node attrNode, Node lastComment) { 419 AttributeInfo info = null; 420 Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$ 421 if (nameNode != null) { 422 String name = nameNode.getNodeValue(); 423 if (name != null) { 424 info = mAttributeMap.get(name); 425 // If the attribute is unknown yet, parse it. 426 // If the attribute is know but its format is unknown, parse it too. 427 if (info == null || info.getFormats().size() == 0) { 428 info = parseAttributeTypes(attrNode, name); 429 if (info != null) { 430 mAttributeMap.put(name, info); 431 } 432 } else if (lastComment != null) { 433 info = new AttributeInfo(info); 434 } 435 if (info != null) { 436 if (lastComment != null) { 437 String nodeValue = lastComment.getNodeValue(); 438 if (nodeValue.contains(DOC_HIDE)) { 439 return null; 440 } 441 info.setJavaDoc(parseJavadoc(nodeValue)); 442 info.setDeprecatedDoc(parseDeprecatedDoc(nodeValue)); 443 } 444 } 445 } 446 } 447 return info; 448 } 449 450 /** 451 * Finds all the attributes for a particular style node, 452 * e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout". 453 * 454 * @param styleName The name of the declare-styleable node 455 * @param declareStyleableNode The declare-styleable node itself 456 */ parseDeclaredStyleable(String styleName, Node declareStyleableNode)457 private DeclareStyleableInfo parseDeclaredStyleable(String styleName, 458 Node declareStyleableNode) { 459 ArrayList<AttributeInfo> attrs = new ArrayList<AttributeInfo>(); 460 Node lastComment = null; 461 for (Node node = declareStyleableNode.getFirstChild(); 462 node != null; 463 node = node.getNextSibling()) { 464 465 switch (node.getNodeType()) { 466 case Node.COMMENT_NODE: 467 lastComment = node; 468 break; 469 case Node.ELEMENT_NODE: 470 if (node.getNodeName().equals("attr")) { //$NON-NLS-1$ 471 AttributeInfo info = parseAttr(node, lastComment); 472 if (info != null) { 473 attrs.add(info); 474 } 475 } 476 lastComment = null; 477 break; 478 } 479 480 } 481 482 return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()])); 483 } 484 485 /** 486 * Returns the {@link AttributeInfo} for a specific <attr> XML node. 487 * This gets the javadoc, the type, the name and the enum/flag values if any. 488 * <p/> 489 * The XML node is expected to have the following attributes: 490 * <ul> 491 * <li>"name", which is mandatory. The node is skipped if this is missing.</li> 492 * <li>"format".</li> 493 * </ul> 494 * The format may be one type or two types (e.g. "reference|color"). 495 * An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute, 496 * they are implicitly stated by the presence of sub-nodes <enum> or <flag>. 497 * <p/> 498 * By design, attr nodes of the same name MUST have the same type. 499 * Attribute nodes are thus cached by name and reused as much as possible. 500 * When reusing a node, it is duplicated and its javadoc reassigned. 501 */ parseAttributeTypes(Node attrNode, String name)502 private AttributeInfo parseAttributeTypes(Node attrNode, String name) { 503 EnumSet<Format> formats = null; 504 String[] enumValues = null; 505 String[] flagValues = null; 506 507 Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$ 508 if (attrFormat != null) { 509 for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$ 510 Format format = mFormatNames.get(f); 511 if (format == null) { 512 mLog.info( 513 "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$ 514 f, name, getOsAttrsXmlPath()); 515 } else if (format != AttributeInfo.Format.ENUM && 516 format != AttributeInfo.Format.FLAG) { 517 if (formats == null) { 518 formats = format.asSet(); 519 } else { 520 if (formats.size() == 1) { 521 formats = EnumSet.copyOf(formats); 522 } 523 formats.add(format); 524 } 525 } 526 } 527 } 528 529 // does this <attr> have <enum> children? 530 enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$ 531 if (enumValues != null) { 532 if (formats == null) { 533 formats = Format.ENUM_SET; 534 } else { 535 if (formats.size() == 1) { 536 formats = EnumSet.copyOf(formats); 537 } 538 formats.add(Format.ENUM); 539 } 540 } 541 542 // does this <attr> have <flag> children? 543 flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$ 544 if (flagValues != null) { 545 if (formats == null) { 546 formats = Format.FLAG_SET; 547 } else { 548 if (formats.size() == 1) { 549 formats = EnumSet.copyOf(formats); 550 } 551 formats.add(Format.FLAG); 552 } 553 } 554 555 if (formats == null) { 556 formats = Format.NONE; 557 } 558 559 AttributeInfo info = new AttributeInfo(name, formats); 560 info.setEnumValues(enumValues); 561 info.setFlagValues(flagValues); 562 return info; 563 } 564 565 /** 566 * Given an XML node that represents an <attr> node, this method searches 567 * if the node has any children nodes named "target" (e.g. "enum" or "flag"). 568 * Such nodes must have a "name" attribute. 569 * <p/> 570 * If "attrNode" is null, look for any <attr> that has the given attrNode 571 * and the requested children nodes. 572 * <p/> 573 * This method collects all the possible names of these children nodes and 574 * return them. 575 * 576 * @param attrNode The <attr> XML node 577 * @param filter The child node to look for, either "enum" or "flag". 578 * @param attrName The value of the name attribute of <attr> 579 * 580 * @return Null if there are no such children nodes, otherwise an array of length >= 1 581 * of all the names of these children nodes. 582 */ parseEnumFlagValues(Node attrNode, String filter, String attrName)583 private String[] parseEnumFlagValues(Node attrNode, String filter, String attrName) { 584 ArrayList<String> names = null; 585 for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) { 586 if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) { 587 Node nameNode = child.getAttributes().getNamedItem("name"); //$NON-NLS-1$ 588 if (nameNode == null) { 589 mLog.warning( 590 "Missing name attribute in <attr name=\"%s\"><%s></attr>", //$NON-NLS-1$ 591 attrName, filter); 592 } else { 593 if (names == null) { 594 names = new ArrayList<String>(); 595 } 596 String name = nameNode.getNodeValue(); 597 names.add(name); 598 599 Node valueNode = child.getAttributes().getNamedItem("value"); //$NON-NLS-1$ 600 if (valueNode == null) { 601 mLog.warning( 602 "Missing value attribute in <attr name=\"%s\"><%s name=\"%s\"></attr>", //$NON-NLS-1$ 603 attrName, filter, name); 604 } else { 605 String value = valueNode.getNodeValue(); 606 try { 607 // Integer.decode cannot handle "ffffffff", see JDK issue 6624867 608 int i = (int) (long) Long.decode(value); 609 610 Map<String, Integer> map = mEnumFlagValues.get(attrName); 611 if (map == null) { 612 map = new HashMap<String, Integer>(); 613 mEnumFlagValues.put(attrName, map); 614 } 615 map.put(name, Integer.valueOf(i)); 616 617 } catch(NumberFormatException e) { 618 mLog.error(e, 619 "Value in <attr name=\"%s\"><%s name=\"%s\" value=\"%s\"></attr> is not a valid decimal or hexadecimal", //$NON-NLS-1$ 620 attrName, filter, name, value); 621 } 622 } 623 } 624 } 625 } 626 return names == null ? null : names.toArray(new String[names.size()]); 627 } 628 629 /** 630 * Parses the javadoc comment. 631 * Only keeps the first sentence. 632 * <p/> 633 * This does not remove nor simplify links and references. 634 */ parseJavadoc(String comment)635 private String parseJavadoc(String comment) { 636 if (comment == null) { 637 return null; 638 } 639 640 // sanitize & collapse whitespace 641 comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ 642 643 // Explicitly remove any @deprecated tags since they are handled separately. 644 comment = comment.replaceAll("(?:\\{@deprecated[^}]*\\}|@deprecated[^@}]*)", ""); 645 646 // take everything up to the first dot that is followed by a space or the end of the line. 647 // I love regexps :-). For the curious, the regexp is: 648 // - start of line 649 // - ignore whitespace 650 // - group: 651 // - everything, not greedy 652 // - non-capturing group (?: ) 653 // - end of string 654 // or 655 // - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" ) 656 // (<! non-capturing zero-width negative look-behind) 657 // - a dot 658 // - followed by a space (?= non-capturing zero-width positive look-ahead) 659 // - anything else is ignored 660 comment = comment.replaceFirst("^\\s*(.*?(?:$|(?<![a-zA-Z]\\.[a-zA-Z])\\.(?=\\s))).*", "$1"); //$NON-NLS-1$ //$NON-NLS-2$ 661 662 return comment; 663 } 664 665 666 /** 667 * Parses the javadoc and extract the first @deprecated tag, if any. 668 * Returns null if there's no @deprecated tag. 669 * The deprecated tag can be of two forms: 670 * - {+@deprecated ...text till the next bracket } 671 * Note: there should be no space or + between { and @. I need one in this comment otherwise 672 * this method will be tagged as deprecated ;-) 673 * - @deprecated ...text till the next @tag or end of the comment. 674 * In both cases the comment can be multi-line. 675 */ parseDeprecatedDoc(String comment)676 private String parseDeprecatedDoc(String comment) { 677 // Skip if we can't even find the tag in the comment. 678 if (comment == null) { 679 return null; 680 } 681 682 // sanitize & collapse whitespace 683 comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ 684 685 int pos = comment.indexOf("{@deprecated"); 686 if (pos >= 0) { 687 comment = comment.substring(pos + 12 /* len of {@deprecated */); 688 comment = comment.replaceFirst("^([^}]*).*", "$1"); 689 } else if ((pos = comment.indexOf("@deprecated")) >= 0) { 690 comment = comment.substring(pos + 11 /* len of @deprecated */); 691 comment = comment.replaceFirst("^(.*?)(?:@.*|$)", "$1"); 692 } else { 693 return null; 694 } 695 696 return comment.trim(); 697 } 698 } 699