Update from Google.
--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
new file mode 100644
index 0000000..0bdc1f1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
@@ -0,0 +1,248 @@
+// Copyright 2014 Google Inc. 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.BlazeRule;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.TriState;
+import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.syntax.Label;
+
+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.
+ */
+class RuleDocumentationAttribute implements Comparable<RuleDocumentationAttribute> {
+
+ private static final Map<Type<?>, String> TYPE_DESC = ImmutableMap.<Type<?>, String>builder()
+ .put(Type.BOOLEAN, "Boolean")
+ .put(Type.INTEGER, "Integer")
+ .put(Type.INTEGER_LIST, "List of Integer")
+ .put(Type.STRING, "String")
+ .put(Type.STRING_LIST, "List of String")
+ .put(Type.TRISTATE, "Integer")
+ .put(Type.LABEL, "<a href=\"build-ref.html#labels\">Label</a>")
+ .put(Type.LABEL_LIST, "List of <a href=\"build-ref.html#labels\">labels</a>")
+ .put(Type.NODEP_LABEL, "<a href=\"build-ref.html#name\">Name</a>")
+ .put(Type.NODEP_LABEL_LIST, "List of <a href=\"build-ref.html#name\">names</a>")
+ .put(Type.OUTPUT, "<a href=\"build-ref.html#filename\">Filename</a>")
+ .put(Type.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;
+ private int startLineCnt;
+ private Set<String> flags;
+
+ /**
+ * 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, Set<String> flags) {
+ return new RuleDocumentationAttribute(definitionClass, attributeName, htmlDocumentation,
+ startLineCnt, flags, null);
+ }
+
+ private RuleDocumentationAttribute(Class<? extends RuleDefinition> definitionClass,
+ String attributeName, String htmlDocumentation, int startLineCnt, 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;
+ }
+
+ /**
+ * Returns the name of the rule attribute.
+ */
+ String getAttributeName() {
+ return attributeName;
+ }
+
+ /**
+ * Returns the raw html documentation of the rule attribute.
+ */
+ String getHtmlDocumentation(Attribute attribute) {
+ // TODO(bazel-team): this is needed for common type attributes. Fix those and remove this.
+ if (attribute == null) {
+ return htmlDocumentation;
+ }
+ StringBuilder sb = new StringBuilder()
+ .append("<i>(")
+ .append(TYPE_DESC.get(attribute.getType()))
+ .append("; " + (attribute.isMandatory() ? "required" : "optional"))
+ .append(getDefaultValue(attribute))
+ .append(")</i><br/>\n");
+ return htmlDocumentation.replace("${" + DocgenConsts.VAR_SYNOPSIS + "}", sb.toString());
+ }
+
+ private String getDefaultValue(Attribute attribute) {
+ String prefix = "; default is ";
+ Object value = attribute.getDefaultValueForTesting();
+ if (value instanceof Boolean) {
+ return prefix + ((Boolean) value ? "1" : "0");
+ } 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 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.
+ */
+ 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) {
+ 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);
+ 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) {
+ BlazeRule ann = usingClass.getAnnotation(BlazeRule.class);
+ if (ann != null) {
+ for (Class<? extends RuleDefinition> ancestor : ann.ancestors()) {
+ if (!visited.containsKey(ancestor)) {
+ toVisit.addLast(ancestor);
+ visited.put(ancestor, visited.get(usingClass) + 1);
+ }
+ }
+ }
+ }
+
+ 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();
+ }
+}