|  | // Copyright 2014 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // 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.devtools.build.docgen; | 
|  |  | 
|  | import com.google.common.base.Preconditions; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import com.google.common.collect.ImmutableSet; | 
|  | import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; | 
|  | import com.google.devtools.build.lib.analysis.RuleDefinition; | 
|  | import com.google.devtools.build.lib.cmdline.Label; | 
|  | import com.google.devtools.build.lib.packages.Attribute; | 
|  | import com.google.devtools.build.lib.packages.BuildType; | 
|  | import com.google.devtools.build.lib.packages.TriState; | 
|  | import com.google.devtools.build.lib.syntax.Type; | 
|  | import java.util.HashMap; | 
|  | import java.util.LinkedList; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  |  | 
|  | /** | 
|  | * A class storing a rule attribute documentation along with some meta information. The class | 
|  | * provides functionality to compute the ancestry level of this attribute's generator rule | 
|  | * definition class compared to other rule definition classes. | 
|  | * | 
|  | * <p>Warning, two RuleDocumentationAttribute objects are equal based on only the attributeName. | 
|  | */ | 
|  | public class RuleDocumentationAttribute | 
|  | implements Comparable<RuleDocumentationAttribute>, Cloneable { | 
|  |  | 
|  | private static final ImmutableMap<Type<?>, String> TYPE_DESC = | 
|  | ImmutableMap.<Type<?>, String>builder() | 
|  | .put(Type.BOOLEAN, "Boolean") | 
|  | .put(Type.INTEGER, "Integer") | 
|  | .put(Type.INTEGER_LIST, "List of integers") | 
|  | .put(Type.STRING, "String") | 
|  | .put(Type.STRING_DICT, "Dictionary: String -> String") | 
|  | .put(Type.STRING_LIST, "List of strings") | 
|  | .put(BuildType.TRISTATE, "Integer") | 
|  | .put(BuildType.LABEL, "<a href=\"../build-ref.html#labels\">Label</a>") | 
|  | .put(BuildType.LABEL_LIST, "List of <a href=\"../build-ref.html#labels\">labels</a>") | 
|  | .put( | 
|  | BuildType.LABEL_DICT_UNARY, | 
|  | "Dictionary mapping strings to <a href=\"../build-ref.html#labels\">labels</a>") | 
|  | .put(BuildType.LICENSE, "Licence type") | 
|  | .put(BuildType.NODEP_LABEL, "<a href=\"../build-ref.html#name\">Name</a>") | 
|  | .put(BuildType.NODEP_LABEL_LIST, "List of <a href=\"../build-ref.html#name\">names</a>") | 
|  | .put(BuildType.OUTPUT, "<a href=\"../build-ref.html#filename\">Filename</a>") | 
|  | .put( | 
|  | BuildType.OUTPUT_LIST, "List of <a href=\"../build-ref.html#filename\">filenames</a>") | 
|  | .build(); | 
|  |  | 
|  | private final Class<? extends RuleDefinition> definitionClass; | 
|  | private final String attributeName; | 
|  | private final String htmlDocumentation; | 
|  | private final String commonType; | 
|  | // Used to expand rule link references in the attribute documentation. | 
|  | private RuleLinkExpander linkExpander; | 
|  | private int startLineCnt; | 
|  | private String fileName; | 
|  | private Set<String> flags; | 
|  | private Attribute attribute; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Creates common RuleDocumentationAttribute such as deps or data. | 
|  | * These attribute docs have no definitionClass or htmlDocumentation (it's in the BE header). | 
|  | */ | 
|  | static RuleDocumentationAttribute create( | 
|  | String attributeName, String commonType, String htmlDocumentation) { | 
|  | RuleDocumentationAttribute docAttribute = new RuleDocumentationAttribute( | 
|  | null, attributeName, htmlDocumentation, 0, "", ImmutableSet.<String>of(), commonType); | 
|  | return docAttribute; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Creates a RuleDocumentationAttribute with all the necessary fields for explicitly | 
|  | * defined rule attributes. | 
|  | */ | 
|  | static RuleDocumentationAttribute create(Class<? extends RuleDefinition> definitionClass, | 
|  | String attributeName, String htmlDocumentation, int startLineCnt, String fileName, | 
|  | Set<String> flags) { | 
|  | return new RuleDocumentationAttribute(definitionClass, attributeName, htmlDocumentation, | 
|  | startLineCnt, fileName, flags, null); | 
|  | } | 
|  |  | 
|  | private RuleDocumentationAttribute(Class<? extends RuleDefinition> definitionClass, | 
|  | String attributeName, String htmlDocumentation, int startLineCnt, String fileName, | 
|  | Set<String> flags, String commonType) { | 
|  | Preconditions.checkNotNull(attributeName, "AttributeName must not be null."); | 
|  | this.definitionClass = definitionClass; | 
|  | this.attributeName = attributeName; | 
|  | this.htmlDocumentation = htmlDocumentation; | 
|  | this.startLineCnt = startLineCnt; | 
|  | this.flags = flags; | 
|  | this.commonType = commonType; | 
|  | this.fileName = fileName; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected Object clone() throws CloneNotSupportedException { | 
|  | return super.clone(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the Attribute object that this documents. | 
|  | */ | 
|  | void setAttribute(Attribute attribute) { | 
|  | this.attribute = attribute; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the name of the rule attribute. | 
|  | */ | 
|  | public String getAttributeName() { | 
|  | return attributeName; | 
|  | } | 
|  |  | 
|  | /** Returns the file name where the rule attribute is defined. */ | 
|  | public String getFileName() { | 
|  | return fileName; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns whether this attribute is marked as deprecated. | 
|  | */ | 
|  | public boolean isDeprecated() { | 
|  | return hasFlag(DocgenConsts.FLAG_DEPRECATED); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets the {@link RuleLinkExpander} to be used to expand links in the HTML documentation. | 
|  | */ | 
|  | public void setRuleLinkExpander(RuleLinkExpander linkExpander) { | 
|  | this.linkExpander = linkExpander; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the html documentation of the rule attribute. | 
|  | */ | 
|  | public String getHtmlDocumentation() throws BuildEncyclopediaDocException { | 
|  | String expandedHtmlDoc = htmlDocumentation; | 
|  | if (linkExpander != null) { | 
|  | try { | 
|  | expandedHtmlDoc = linkExpander.expand(expandedHtmlDoc); | 
|  | } catch (IllegalArgumentException e) { | 
|  | throw new BuildEncyclopediaDocException(fileName, startLineCnt, e.getMessage()); | 
|  | } | 
|  | } | 
|  | return expandedHtmlDoc; | 
|  | } | 
|  |  | 
|  | /** Returns whether the param is required or optional. */ | 
|  | public boolean isMandatory() { | 
|  | if (attribute == null) { | 
|  | return false; | 
|  | } | 
|  | return attribute.isMandatory(); | 
|  | } | 
|  |  | 
|  | private String getDefaultValue() { | 
|  | if (attribute == null) { | 
|  | return ""; | 
|  | } | 
|  | String prefix = "; default is "; | 
|  | Object value = attribute.getDefaultValueUnchecked(); | 
|  | if (value instanceof Boolean) { | 
|  | return prefix + ((Boolean) value ? "True" : "False"); | 
|  | } else if (value instanceof Integer) { | 
|  | return prefix + String.valueOf(value); | 
|  | } else if (value instanceof String && !(((String) value).isEmpty())) { | 
|  | return prefix + "\"" + value + "\""; | 
|  | } else if (value instanceof TriState) { | 
|  | switch((TriState) value) { | 
|  | case AUTO: | 
|  | return prefix + "-1"; | 
|  | case NO: | 
|  | return prefix + "0"; | 
|  | case YES: | 
|  | return prefix + "1"; | 
|  | } | 
|  | } else if (value instanceof Label) { | 
|  | return prefix + "<code>" + value + "</code>"; | 
|  | } | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns a string containing the synopsis for this attribute. | 
|  | */ | 
|  | public String getSynopsis() { | 
|  | if (attribute == null) { | 
|  | return ""; | 
|  | } | 
|  | StringBuilder sb = new StringBuilder() | 
|  | .append(TYPE_DESC.get(attribute.getType())) | 
|  | .append("; " + (attribute.isMandatory() ? "required" : "optional")) | 
|  | .append(!attribute.isConfigurable() | 
|  | ? String.format("; <a href=\"%s#configurable-attributes\">nonconfigurable</a>", | 
|  | RuleDocumentation.COMMON_DEFINITIONS_PAGE) | 
|  | : "") | 
|  | .append(getDefaultValue()); | 
|  | return sb.toString(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the number of first line of the attribute documentation in its declaration file. | 
|  | */ | 
|  | int getStartLineCnt() { | 
|  | return startLineCnt; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if the attribute doc is of a common attribute type. | 
|  | */ | 
|  | public boolean isCommonType() { | 
|  | return commonType != null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the common attribute type if this attribute doc is of a common type | 
|  | * otherwise actualRule. | 
|  | */ | 
|  | String getGeneratedInRule(String actualRule) { | 
|  | return isCommonType() ? commonType : actualRule; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if this attribute documentation has the parameter flag. | 
|  | */ | 
|  | boolean hasFlag(String flag) { | 
|  | return flags.contains(flag); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the length of a shortest path from usingClass to the definitionClass of this | 
|  | * RuleDocumentationAttribute in the rule definition ancestry graph. Returns -1 | 
|  | * if definitionClass is not the ancestor (transitively) of usingClass. | 
|  | */ | 
|  | int getDefinitionClassAncestryLevel(Class<? extends RuleDefinition> usingClass, | 
|  | ConfiguredRuleClassProvider ruleClassProvider) { | 
|  | if (usingClass.equals(definitionClass)) { | 
|  | return 0; | 
|  | } | 
|  | // Storing nodes (rule class definitions) with the length of the shortest path from usingClass | 
|  | Map<Class<? extends RuleDefinition>, Integer> visited = new HashMap<>(); | 
|  | LinkedList<Class<? extends RuleDefinition>> toVisit = new LinkedList<>(); | 
|  | visited.put(usingClass, 0); | 
|  | toVisit.add(usingClass); | 
|  | // Searching the shortest path from usingClass to this.definitionClass using BFS | 
|  | do { | 
|  | Class<? extends RuleDefinition> ancestor = toVisit.removeFirst(); | 
|  | visitAncestor(ancestor, visited, toVisit, ruleClassProvider); | 
|  | if (ancestor.equals(definitionClass)) { | 
|  | return visited.get(ancestor); | 
|  | } | 
|  | } while (!toVisit.isEmpty()); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | private void visitAncestor( | 
|  | Class<? extends RuleDefinition> usingClass, | 
|  | Map<Class<? extends RuleDefinition>, Integer> visited, | 
|  | LinkedList<Class<? extends RuleDefinition>> toVisit, | 
|  | ConfiguredRuleClassProvider ruleClassProvider) { | 
|  | RuleDefinition instance = getRuleDefinition(usingClass, ruleClassProvider); | 
|  | for (Class<? extends RuleDefinition> ancestor : instance.getMetadata().ancestors()) { | 
|  | if (!visited.containsKey(ancestor)) { | 
|  | toVisit.addLast(ancestor); | 
|  | visited.put(ancestor, visited.get(usingClass) + 1); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private RuleDefinition getRuleDefinition(Class<? extends RuleDefinition> usingClass, | 
|  | ConfiguredRuleClassProvider ruleClassProvider) { | 
|  | if (ruleClassProvider == null) { | 
|  | try { | 
|  | return usingClass.getConstructor().newInstance(); | 
|  | } catch (ReflectiveOperationException | IllegalArgumentException e) { | 
|  | throw new IllegalStateException(e); | 
|  | } | 
|  | } | 
|  | return ruleClassProvider.getRuleClassDefinition(usingClass.getName()); | 
|  | } | 
|  |  | 
|  | private int getAttributeOrderingPriority(RuleDocumentationAttribute attribute) { | 
|  | if (DocgenConsts.ATTRIBUTE_ORDERING.containsKey(attribute.attributeName)) { | 
|  | return DocgenConsts.ATTRIBUTE_ORDERING.get(attribute.attributeName); | 
|  | } else { | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int compareTo(RuleDocumentationAttribute o) { | 
|  | int thisPriority = getAttributeOrderingPriority(this); | 
|  | int otherPriority = getAttributeOrderingPriority(o); | 
|  | if (thisPriority > otherPriority) { | 
|  | return 1; | 
|  | } else if (thisPriority < otherPriority) { | 
|  | return -1; | 
|  | } else { | 
|  | return this.attributeName.compareTo(o.attributeName); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean equals(Object obj) { | 
|  | if (this == obj) { | 
|  | return true; | 
|  | } | 
|  | if (!(obj instanceof RuleDocumentationAttribute)) { | 
|  | return false; | 
|  | } | 
|  | return attributeName.equals(((RuleDocumentationAttribute) obj).attributeName); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int hashCode() { | 
|  | return attributeName.hashCode(); | 
|  | } | 
|  | } |